mirror of
https://github.com/nextcloud/server.git
synced 2026-05-28 04:32:30 -04:00
Merge pull request #60665 from nextcloud/feat/59888-nav-redesign-header-search-launcher
feat(core): Add centered search input to top bar
This commit is contained in:
commit
9ecf114443
7 changed files with 201 additions and 27 deletions
|
|
@ -11,9 +11,9 @@
|
|||
:triggers="[]"
|
||||
placement="bottom-start"
|
||||
:skidding="popoverSkidding"
|
||||
:setReturnFocus="returnFocusTarget"
|
||||
popoverBaseClass="app-menu__popover-base"
|
||||
popupRole="menu"
|
||||
:set-return-focus="returnFocusTarget"
|
||||
popover-base-class="app-menu__popover-base"
|
||||
popup-role="menu"
|
||||
@update:shown="opened = $event">
|
||||
<template #trigger>
|
||||
<NcButton
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
ref="items"
|
||||
:app="item"
|
||||
:outlined="item.id === 'more-apps' || item.id === 'app-store'"
|
||||
:newTab="item.id === 'app-store'"
|
||||
:new-tab="item.id === 'app-store'"
|
||||
:tabindex="i === focusedIndex ? 0 : -1" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -431,8 +431,7 @@ export default defineComponent({
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
// Hide on small screens (matches $breakpoint-small-mobile in @nextcloud/vue).
|
||||
@media only screen and (max-width: 512px) {
|
||||
@media only screen and (max-width: 1024px) {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
185
core/src/components/UnifiedSearch/UnifiedSearchInput.vue
Normal file
185
core/src/components/UnifiedSearch/UnifiedSearchInput.vue
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
<!--
|
||||
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
-->
|
||||
<template>
|
||||
<search class="unified-search-input" :class="[{ 'unified-search-input--mobile': isSmallMobile }]">
|
||||
<NcHeaderButton
|
||||
v-if="isSmallMobile"
|
||||
:aria-label="placeholderText"
|
||||
aria-haspopup="dialog"
|
||||
:aria-expanded="expanded ? 'true' : 'false'"
|
||||
@click="$emit('click', $event)">
|
||||
<template #icon>
|
||||
<IconMagnify :size="20" />
|
||||
</template>
|
||||
</NcHeaderButton>
|
||||
<button
|
||||
v-else
|
||||
type="button"
|
||||
class="unified-search-input__button"
|
||||
aria-haspopup="dialog"
|
||||
:aria-expanded="expanded ? 'true' : 'false'"
|
||||
@click="$emit('click', $event)">
|
||||
<IconMagnify
|
||||
class="unified-search-input__icon"
|
||||
:size="20"
|
||||
aria-hidden="true" />
|
||||
<span class="unified-search-input__label">
|
||||
{{ placeholderText }}
|
||||
</span>
|
||||
</button>
|
||||
</search>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { useIsSmallMobile } from '@nextcloud/vue/composables/useIsMobile'
|
||||
import NcHeaderButton from '@nextcloud/vue/components/NcHeaderButton'
|
||||
import IconMagnify from 'vue-material-design-icons/Magnify.vue'
|
||||
|
||||
/**
|
||||
* First phase of the unified-search input: a button styled to look like an
|
||||
* input field that opens the unified-search modal on click. A later phase
|
||||
* will replace the button with a real input that filters results inline.
|
||||
*
|
||||
* Implemented as a custom component because no `@nextcloud/vue` component
|
||||
* fits the design role here: NcInputField is a real input whose styling
|
||||
* assumes a light page background and clashes with the themed header,
|
||||
* and NcTextField has the same issue. On narrow viewports the trigger
|
||||
* collapses to a standard NcHeaderButton so it matches the visual
|
||||
* language of the other header items.
|
||||
*/
|
||||
|
||||
defineProps<{
|
||||
/** Whether the popup the input controls is currently open. Bound to aria-expanded. */
|
||||
expanded?: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
click: [mouseEvent: MouseEvent]
|
||||
}>()
|
||||
|
||||
const isSmallMobile = useIsSmallMobile()
|
||||
const placeholderText = t('core', 'Search apps, files, tags, messages …')
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.unified-search-input {
|
||||
&:not(.unified-search-input--mobile) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
inset-inline: 0;
|
||||
margin-inline: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: clamp(200px, 35vw, 600px);
|
||||
max-width: calc(100% - 32px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&--mobile {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
&__button {
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
height: calc(var(--default-clickable-area) - 8px);
|
||||
padding: 0 12px;
|
||||
border: none;
|
||||
border-radius: var(--border-radius-element, 8px);
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
-webkit-backdrop-filter: var(--filter-background-blur);
|
||||
backdrop-filter: var(--filter-background-blur);
|
||||
box-shadow: inset 0 2px 0 rgba(0, 0, 0, 0.12);
|
||||
color: var(--color-background-plain-text);
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
font: inherit;
|
||||
transition: background-color var(--animation-quick) ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
background-color: rgba(0, 0, 0, 0.22);
|
||||
outline: 2px solid var(--color-background-plain-text);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: rgba(0, 0, 0, 0.28) !important;
|
||||
color: var(--color-background-plain-text) !important;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__label {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.unified-search-input--mobile :deep(.header-menu) {
|
||||
height: var(--default-clickable-area);
|
||||
}
|
||||
|
||||
.unified-search-input--mobile :deep(.header-menu__trigger) {
|
||||
--button-size: var(--default-clickable-area) !important;
|
||||
height: var(--default-clickable-area) !important;
|
||||
}
|
||||
|
||||
.unified-search-input--mobile :deep(.button-vue) {
|
||||
--color-main-text: var(--color-background-plain-text);
|
||||
color: var(--color-background-plain-text);
|
||||
border-radius: var(--border-radius-element) !important;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background-color: rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
background-color: rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
background-color: rgba(0, 0, 0, 0.1) !important;
|
||||
outline: none !important;
|
||||
box-shadow: inset 0 0 0 2px var(--color-background-plain-text) !important;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme-dark] .unified-search-input__button,
|
||||
[data-theme-dark-highcontrast] .unified-search-input__button {
|
||||
background-color: color-mix(in srgb, var(--color-primary-element) 16%, transparent);
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(in srgb, var(--color-primary-element) 22%, transparent);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
background-color: color-mix(in srgb, var(--color-primary-element) 22%, transparent);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: color-mix(in srgb, var(--color-primary-element) 28%, transparent) !important;
|
||||
color: var(--color-background-plain-text) !important;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -4,15 +4,9 @@
|
|||
-->
|
||||
<template>
|
||||
<div class="unified-search-menu">
|
||||
<NcHeaderButton
|
||||
v-show="!showLocalSearch"
|
||||
id="unified-search"
|
||||
:aria-label="t('core', 'Unified search')"
|
||||
@click="toggleUnifiedSearch">
|
||||
<template #icon>
|
||||
<NcIconSvgWrapper :path="mdiMagnify" />
|
||||
</template>
|
||||
</NcHeaderButton>
|
||||
<UnifiedSearchInput
|
||||
:expanded="showUnifiedSearch || showLocalSearch"
|
||||
@click="toggleUnifiedSearch" />
|
||||
<UnifiedSearchLocalSearchBar
|
||||
v-if="supportsLocalSearch"
|
||||
:open.sync="showLocalSearch"
|
||||
|
|
@ -26,14 +20,12 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { mdiMagnify } from '@mdi/js'
|
||||
import { emit, subscribe } from '@nextcloud/event-bus'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { useBrowserLocation } from '@vueuse/core'
|
||||
import debounce from 'debounce'
|
||||
import { defineComponent } from 'vue'
|
||||
import NcHeaderButton from '@nextcloud/vue/components/NcHeaderButton'
|
||||
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
|
||||
import UnifiedSearchInput from '../components/UnifiedSearch/UnifiedSearchInput.vue'
|
||||
import UnifiedSearchLocalSearchBar from '../components/UnifiedSearch/UnifiedSearchLocalSearchBar.vue'
|
||||
import UnifiedSearchModal from '../components/UnifiedSearch/UnifiedSearchModal.vue'
|
||||
import logger from '../logger.js'
|
||||
|
|
@ -42,10 +34,9 @@ export default defineComponent({
|
|||
name: 'UnifiedSearch',
|
||||
|
||||
components: {
|
||||
NcHeaderButton,
|
||||
NcIconSvgWrapper,
|
||||
UnifiedSearchModal,
|
||||
UnifiedSearchLocalSearchBar,
|
||||
UnifiedSearchInput,
|
||||
},
|
||||
|
||||
setup() {
|
||||
|
|
@ -54,7 +45,6 @@ export default defineComponent({
|
|||
return {
|
||||
currentLocation,
|
||||
|
||||
mdiMagnify,
|
||||
t,
|
||||
}
|
||||
},
|
||||
|
|
|
|||
4
dist/core-main.js
vendored
4
dist/core-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-main.js.map
vendored
2
dist/core-main.js.map
vendored
File diff suppressed because one or more lines are too long
4
dist/core-unified-search.js
vendored
4
dist/core-unified-search.js
vendored
File diff suppressed because one or more lines are too long
2
dist/core-unified-search.js.map
vendored
2
dist/core-unified-search.js.map
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue