Files
skt-vuetify-templates/src/components/layouts/main-layout/AppBarTopCol.vue
T

332 lines
8.9 KiB
Vue

<template>
<v-col class="d-flex align-center bg-surface pr-2 pl-2 pl-m-3 py-1">
<v-btn
v-if="isMobile"
:icon="mdiMenu"
size="small"
variant="text"
@click="emit('toggle-drawer')"
></v-btn>
<div v-if="features.showSearch" class="search-input-wrapper">
<span id="admin-layout-search-label" class="sr-only">{{ searchConfig.label }}</span>
<v-text-field
v-model="searchValueModel"
aria-labelledby="admin-layout-search-label"
:aria-label="searchConfig.label"
class="search-input"
density="compact"
hide-details
name="layout-search"
:placeholder="searchConfig.placeholder"
variant="outlined"
@keyup.enter="triggerSearch"
>
<template v-if="!isMobile" #prepend-inner>
<v-icon size="small" :icon="mdiMagnify" />
</template>
<template #append-inner>
<v-btn
:aria-label="searchConfig.label"
color="primary"
size="small"
variant="text"
@click="triggerSearch"
>
開始搜尋
</v-btn>
</template>
</v-text-field>
</div>
<div v-if="features.showToolbarActions" class="top-actions">
<slot name="actions">
<!-- 通知 -->
<v-tooltip
location="bottom"
:text="toolbarActions.notificationsLabel"
:aria-label="toolbarActions.notificationsLabel"
>
<template #activator="{ props: activatorProps }">
<v-btn
v-bind="activatorProps"
:aria-label="toolbarActions.notificationsLabel"
icon
size="small"
variant="text"
@click="emit('action', 'notifications')"
>
<v-badge
v-if="toolbarCounts.notifications"
color="error"
:content="toolbarCounts.notifications"
offset-x="4"
offset-y="-2"
>
<v-icon :icon="mdiBellOutline" />
</v-badge>
<v-icon v-else :icon="mdiBellOutline" />
</v-btn>
</template>
</v-tooltip>
<!-- 訊息 -->
<v-tooltip
location="bottom"
:text="toolbarActions.messagesLabel"
:aria-label="toolbarActions.messagesLabel"
>
<template #activator="{ props: activatorProps }">
<v-btn
v-bind="activatorProps"
:aria-label="toolbarActions.messagesLabel"
icon
size="small"
variant="text"
@click="emit('action', 'messages')"
>
<v-badge
v-if="toolbarCounts.messages"
color="warning"
:content="toolbarCounts.messages"
offset-x="4"
offset-y="-2"
>
<v-icon :icon="mdiMessageTextOutline" />
</v-badge>
<v-icon v-else :icon="mdiMessageTextOutline" />
</v-btn>
</template>
</v-tooltip>
<!-- 說明 -->
<v-tooltip v-if="false" location="bottom" :text="toolbarActions.helpLabel">
<template #activator="{ props: activatorProps }">
<v-btn
v-bind="activatorProps"
:aria-label="toolbarActions.helpLabel"
icon
size="small"
variant="text"
@click="emit('action', 'help')"
>
<v-icon :icon="mdiHelp" />
</v-btn>
</template>
</v-tooltip>
<!-- 設定 -->
<v-menu :close-on-content-click="false" location="bottom end">
<template #activator="{ props: menuProps }">
<v-tooltip
location="bottom"
:text="toolbarActions.settingsLabel"
:aria-label="toolbarActions.settingsLabel"
>
<template #activator="{ props: tooltipProps }">
<v-btn
v-bind="{ ...menuProps, ...tooltipProps }"
:aria-label="toolbarActions.settingsLabel"
icon
size="small"
variant="text"
>
<v-icon :icon="mdiCogOutline" />
</v-btn>
</template>
</v-tooltip>
</template>
<v-list role="none" density="compact" width="180">
<v-list-subheader class="text-subtitle-1 py-2">顯示設定</v-list-subheader>
<v-list-item>
<v-switch
v-model="showFavoritesBarModel"
color="primary"
density="comfortable"
hide-details
>
<template #label>
<span class="text-body-2" style="width: 8ch">常用功能</span>
</template>
</v-switch>
</v-list-item>
<v-list-item>
<v-switch
v-model="breadcrumbBarVisibleModel"
color="primary"
density="comfortable"
hide-details
>
<template #label>
<span class="text-body-2" style="width: 8ch">路徑</span>
</template>
</v-switch>
</v-list-item>
</v-list>
</v-menu>
<!-- 登出 -->
<v-tooltip location="bottom" :text="logoutLabel" :aria-label="logoutLabel">
<template #activator="{ props: activatorProps }">
<v-btn
v-bind="activatorProps"
:aria-label="logoutLabel"
icon
size="small"
variant="text"
@click="emit('logout')"
>
<v-icon :icon="mdiLogout" />
</v-btn>
</template>
</v-tooltip>
<v-tooltip
v-if="features.showThemeToggle"
location="bottom"
:text="themeToggleLabel"
:aria-label="themeToggleLabel"
>
<template #activator="{ props: activatorProps }">
<v-btn
v-bind="activatorProps"
:aria-label="themeToggleLabel"
icon
variant="text"
@click="emit('toggle-theme')"
>
<v-icon :icon="mdiPaletteOutline" />
</v-btn>
</template>
</v-tooltip>
</slot>
</div>
</v-col>
</template>
<script setup lang="ts">
import type {
AdminLayoutActionType,
AdminLayoutFeatures,
AdminLayoutSearchConfig,
AdminLayoutToolbarActions,
AdminLayoutToolbarCounts,
} from './types'
import {
mdiBellOutline,
mdiCogOutline,
mdiHelp,
mdiLogout,
mdiMagnify,
mdiMenu,
mdiMessageTextOutline,
mdiPaletteOutline,
} from '@mdi/js'
import { computed } from 'vue'
interface Props {
isMobile?: boolean
features?: AdminLayoutFeatures
searchValue?: string
searchConfig?: AdminLayoutSearchConfig
toolbarActions?: AdminLayoutToolbarActions
toolbarCounts?: AdminLayoutToolbarCounts
logoutLabel?: string
themeToggleLabel?: string
showFavoritesBar?: boolean
breadcrumbBarVisible?: boolean
}
const props = withDefaults(defineProps<Props>(), {
isMobile: false,
features: () => ({
showThemeToggle: false,
showFavorites: true,
showBreadcrumb: true,
showSearch: true,
showToolbarActions: true,
showUserInfo: true,
}),
searchValue: '',
searchConfig: () => ({
placeholder: '',
label: '',
}),
toolbarActions: () => ({
notificationsLabel: '',
messagesLabel: '',
helpLabel: '',
settingsLabel: '',
}),
toolbarCounts: () => ({
notifications: 0,
messages: 0,
}),
logoutLabel: '',
themeToggleLabel: '',
showFavoritesBar: true,
breadcrumbBarVisible: true,
})
const emit = defineEmits<{
'toggle-drawer': []
'update:searchValue': [value: string]
search: []
action: [type: AdminLayoutActionType]
logout: []
'toggle-theme': []
'update:show-favorites-bar': [value: boolean]
'update:breadcrumb-bar-visible': [value: boolean]
}>()
const searchValueModel = computed({
get: () => props.searchValue,
set: (value) => emit('update:searchValue', value),
})
const showFavoritesBarModel = computed({
get: () => props.showFavoritesBar,
set: (value) => emit('update:show-favorites-bar', value),
})
const breadcrumbBarVisibleModel = computed({
get: () => props.breadcrumbBarVisible,
set: (value) => emit('update:breadcrumb-bar-visible', value),
})
function triggerSearch() {
emit('search')
}
</script>
<style scoped>
.search-input-wrapper {
flex: 1;
display: flex;
align-items: center;
gap: 8px;
}
:deep(.search-input-wrapper .v-field--appended) {
padding-inline-end: 4px;
}
.top-actions {
display: flex;
align-items: center;
gap: 8px;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>