Skip to content

Commit

Permalink
Merge pull request #10098 from nextcloud/backport/9997/stable27
Browse files Browse the repository at this point in the history
[stable27] fix(searchBox) - Conversation search UX improvements
  • Loading branch information
DorraJaouad authored Aug 3, 2023
2 parents ce11cec + 6a831e7 commit 55e4c1a
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 73 deletions.
1 change: 0 additions & 1 deletion src/components/LeftSidebar/LeftSidebar.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,6 @@ describe('LeftSidebar.vue', () => {
await flushPromises()

await searchBox.find('input[type="text"]').setValue(searchTerm)
expect(searchBox.props('isSearching')).toBeTruthy()

await flushPromises()

Expand Down
181 changes: 114 additions & 67 deletions src/components/LeftSidebar/LeftSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,77 +27,81 @@
:class="{'conversations-search--expanded': isFocused}">
<SearchBox ref="searchBox"
:value.sync="searchText"
:is-searching="isSearching"
:is-focused="isFocused"
@focus="setIsFocused"
@blur="setIsFocused"
@trailing-blur="setIsFocused"
@input="debounceFetchSearchResults"
@abort-search="abortSearch" />
</div>

<!-- Filters -->
<div class="filters"
:class="{'hidden-visually': isFocused}">
<NcActions class="filter-actions"
:primary="isFiltered !== null">
<template #icon>
<FilterIcon :size="15" />
</template>
<NcActionButton close-after-click
class="filter-actions__button"
:class="{'filter-actions__button--active': isFiltered === 'mentions'}"
@click="handleFilter('mentions')">
<template #icon>
<AtIcon :size="20" />
</template>
{{ t('spreed','Filter unread mentions') }}
</NcActionButton>

<NcActionButton close-after-click
class="filter-actions__button"
:class="{'filter-actions__button--active': isFiltered === 'unread'}"
@click="handleFilter('unread')">
<template #icon>
<MessageBadge :size="20" />
</template>
{{ t('spreed','Filter unread messages') }}
</NcActionButton>

<NcActionButton v-if="isFiltered"
close-after-click
class="filter-actions__clearbutton"
@click="handleFilter(null)">
<transition-group name="radial-reveal">
<!-- Filters -->
<div v-show="!isFocused" key="filters" class="filters">
<NcActions class="filter-actions"
:primary="isFiltered !== null">
<template #icon>
<FilterRemoveIcon :size="20" />
<FilterIcon :size="15" />
</template>
{{ t('spreed', 'Clear filters') }}
</NcActionButton>
</NcActions>
</div>

<!-- Actions -->
<div class="actions">
<NcActions class="conversations-actions">
<template #icon>
<DotsVertical :size="20" />
</template>
<NcActionButton v-if="canStartConversations"
close-after-click
@click="showModalNewConversation">
<template #icon>
<Plus :size="20" />
</template>
{{ t('spreed','Create a new conversation') }}
</NcActionButton>
<NcActionButton close-after-click
class="filter-actions__button"
:class="{'filter-actions__button--active': isFiltered === 'mentions'}"
@click="handleFilter('mentions')">
<template #icon>
<AtIcon :size="20" />
</template>
{{ t('spreed','Filter unread mentions') }}
</NcActionButton>

<NcActionButton close-after-click
class="filter-actions__button"
:class="{'filter-actions__button--active': isFiltered === 'unread'}"
@click="handleFilter('unread')">
<template #icon>
<MessageBadge :size="20" />
</template>
{{ t('spreed','Filter unread messages') }}
</NcActionButton>

<NcActionButton v-if="isFiltered"
close-after-click
class="filter-actions__clearbutton"
@click="handleFilter(null)">
<template #icon>
<FilterRemoveIcon :size="20" />
</template>
{{ t('spreed', 'Clear filters') }}
</NcActionButton>
</NcActions>
</div>

<NcActionButton close-after-click
@click="showModalListConversations">
<!-- Actions -->
<div v-show="!isFocused"
key="actions"
class="actions">
<NcActions class="conversations-actions">
<template #icon>
<List :size="20" />
<DotsVertical :size="20" />
</template>
{{ t('spreed','Join open conversations') }}
</NcActionButton>
</NcActions>
</div>
<NcActionButton v-if="canStartConversations"
close-after-click
@click="showModalNewConversation">
<template #icon>
<Plus :size="20" />
</template>
{{ t('spreed','Create a new conversation') }}
</NcActionButton>

<NcActionButton close-after-click
@click="showModalListConversations">
<template #icon>
<List :size="20" />
</template>
{{ t('spreed','Join open conversations') }}
</NcActionButton>
</NcActions>
</div>
</transition-group>

<!-- All open conversations list -->
<OpenConversationsList ref="openConversationsList" />
Expand All @@ -111,6 +115,17 @@
<ul ref="scroller"
class="scroller"
@scroll="debounceHandleScroll">
<NcListItem v-if="noMatchFound && searchText"
:title="t('spreed', 'Create a new conversation')"
@click="createConversation(searchText)">
<template #icon>
<ChatPlus :size="30" />
</template>
<template #subtitle>
{{ searchText }}
</template>
</NcListItem>

<NcAppNavigationCaption :class="{'hidden-visually': !isSearching}"
:title="t('spreed', 'Conversations')" />
<Conversation v-for="item of conversationsList"
Expand Down Expand Up @@ -146,7 +161,7 @@
<NcAppNavigationCaption v-if="searchResultsUsers.length === 0"
:title="t('spreed', 'Users')" />
<Hint v-if="contactsLoading" :hint="t('spreed', 'Loading')" />
<Hint v-else :hint="t('spreed', 'No search results')" />
<Hint v-else :hint="t('spreed', 'No matches found')" />
</template>
</template>
<template v-if="showStartConversationsOptions">
Expand Down Expand Up @@ -208,6 +223,7 @@ import debounce from 'debounce'
import { ref } from 'vue'
import AtIcon from 'vue-material-design-icons/At.vue'
import ChatPlus from 'vue-material-design-icons/ChatPlus.vue'
import DotsVertical from 'vue-material-design-icons/DotsVertical.vue'
import FilterIcon from 'vue-material-design-icons/Filter.vue'
import FilterRemoveIcon from 'vue-material-design-icons/FilterRemove.vue'
Expand Down Expand Up @@ -238,6 +254,7 @@ import SearchBox from './SearchBox/SearchBox.vue'
import { useArrowNavigation } from '../../composables/useArrowNavigation.js'
import { CONVERSATION } from '../../constants.js'
import {
createPrivateConversation,
searchPossibleConversations,
searchListedConversations,
} from '../../services/conversationsService.js'
Expand Down Expand Up @@ -267,6 +284,7 @@ export default {
FilterIcon,
FilterRemoveIcon,
Plus,
ChatPlus,
List,
DotsVertical,
},
Expand Down Expand Up @@ -319,7 +337,7 @@ export default {
computed: {
conversationsList() {
let conversations = this.$store.getters.conversationsList
if (this.searchText !== '') {
if (this.searchText !== '' || this.isFocused) {
const lowerSearchText = this.searchText.toLowerCase()
conversations = conversations.filter(conversation =>
conversation.displayName.toLowerCase().includes(lowerSearchText)
Expand Down Expand Up @@ -434,11 +452,10 @@ export default {
},
setIsFocused(event) {
if (event.relatedTarget?.className.includes('input-field__clear-button') || this.searchText !== '') {
if (this.searchText !== '') {
return
}
this.isFocused = event.type === 'focus'
},
handleFilter(filter) {
Expand Down Expand Up @@ -551,6 +568,17 @@ export default {
}
},
async createConversation(name) {
const response = await createPrivateConversation(name)
const conversation = response.data.ocs.data
this.$store.dispatch('addConversation', conversation)
this.abortSearch()
this.$router.push({
name: 'conversation',
params: { token: conversation.token },
}).catch(err => console.debug(`Error while pushing the new conversation's route: ${err}`))
},
hasOneToOneConversationWith(userId) {
return !!this.conversationsList.find(conversation => conversation.type === CONVERSATION.TYPE.ONE_TO_ONE && conversation.name === userId)
},
Expand Down Expand Up @@ -783,7 +811,7 @@ export default {
}
.conversations-search {
transition: all 0.3s ease;
transition: all 0.15s ease;
z-index: 1;
// New conversation button width : 52 px
// Filters button width : 44 px
Expand All @@ -796,22 +824,23 @@ export default {
}
&--expanded {
// Gets expanded : 100 % - (52px + 1px)
width : calc(100% - 53px );
width : calc(100% - 8px);
}
}
.filters {
position: absolute;
right : 52px; // New conversation button's width
top : 5px;
display: flex;
height: var(--default-clickable-area);
}
.actions {
position: absolute;
right: 5px;
top : 5px;
}
.filter-actions__button--active {
Expand All @@ -827,6 +856,24 @@ export default {
justify-content: flex-start !important;
}
.radial-reveal-enter-active {
animation: radial-reveal 0.15s forwards;
}
@keyframes radial-reveal {
0% {
transform: scale(0); /* Start as a point */
opacity: 0;
}
100% {
transform: scale(1); /* Expand to full size */
opacity: 1;
}
}
:deep(.input-field__clear-button) {
border-radius: var(--border-radius-pill) !important;
}
:deep(.app-navigation ul) {
padding: 0 !important;
}
Expand Down
28 changes: 23 additions & 5 deletions src/components/LeftSidebar/SearchBox/SearchBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@
<NcTextField ref="searchConversations"
:value="value"
:label="placeholderText"
:show-trailing-button="isSearching"
:show-trailing-button="isFocused"
trailing-button-icon="close"
v-on="$listeners"
v-on="listeners"
@blur="handleBlur"
@update:value="updateValue"
@trailing-button-click="abortSearch"
@keydown.esc="abortSearch">
Expand Down Expand Up @@ -64,17 +65,23 @@ export default {
/**
* If true, this component displays an 'x' button to abort the search
*/
isSearching: {
isFocused: {
type: Boolean,
default: false,
},
},
expose: ['focus'],
emits: ['update:value', 'input', 'submit', 'abort-search'],
emits: ['update:value', 'input', 'submit', 'abort-search', 'blur', 'trailing-blur'],
computed: {
listeners() {
return Object.assign({}, this.$listeners, {
blur: this.handleBlur,
})
},
cancelSearchLabel() {
return t('spreed', 'Cancel search')
},
Expand Down Expand Up @@ -110,8 +117,19 @@ export default {
*/
abortSearch() {
this.$emit('abort-search')
this.focus()
},
handleBlur(event) {
if ((event.relatedTarget) && (Array.from(event.relatedTarget.classList).includes('input-field__clear-button'))) {
event.preventDefault()
this.$refs.searchConversations.$el.querySelector('.input-field__clear-button').addEventListener('blur', (event) => {
this.$emit('trailing-blur', event)
})
} else {
this.$emit('blur', event)
}
},
},
}
</script>
Expand Down

0 comments on commit 55e4c1a

Please sign in to comment.