feat: add nav more menu on mobile (#322)

Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
Ayaka Rizumu 2022-12-04 22:17:02 +08:00 committed by GitHub
parent 8f32b1ce22
commit cbd5867275
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 237 additions and 54 deletions

View file

@ -1,5 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { dropdownContextKey } from './ctx' import { dropdownContextKey } from './ctx'
defineProps<{
placement?: string
}>()
const dropdown = $ref<any>() const dropdown = $ref<any>()
@ -9,7 +12,7 @@ provide(dropdownContextKey, {
</script> </script>
<template> <template>
<VDropdown v-bind="$attrs" ref="dropdown" :class="{ dark: isDark }"> <VDropdown v-bind="$attrs" ref="dropdown" :class="{ dark: isDark }" :placement="placement || 'auto'">
<slot /> <slot />
<template #popper="scope"> <template #popper="scope">
<slot name="popper" v-bind="scope" /> <slot name="popper" v-bind="scope" />

View file

@ -1,30 +1,58 @@
<script setup lang="ts"> <script setup lang="ts">
// only one icon can be lit up at the same time
const moreMenuVisible = ref(false)
</script> </script>
<template> <template>
<nav h-14 border="t base" flex flex-row> <nav
h-14 border="t base" flex flex-row text-xl
of-y-scroll overscroll-none
class="scrollbar-hide after-content-empty after:(h-[calc(100%+0.5px)] w-0.1px pointer-events-none)"
>
<!-- These weird styles above are used for scroll locking, don't change it unless you know exactly what you're doing. -->
<template v-if="currentUser"> <template v-if="currentUser">
<NuxtLink to="/home" active-class="text-primary" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop"> <NuxtLink to="/home" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
<div i-ri:home-5-line /> <div i-ri:home-5-line />
</NuxtLink> </NuxtLink>
<NuxtLink to="/notifications" active-class="text-primary" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop"> <NuxtLink to="/notifications" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
<div i-ri:notification-4-line /> <div i-ri:notification-4-line />
</NuxtLink> </NuxtLink>
</template> </template>
<NuxtLink to="/explore" active-class="text-primary" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop"> <NuxtLink to="/explore" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
<div i-ri:hashtag /> <div i-ri:hashtag />
</NuxtLink> </NuxtLink>
<NuxtLink group to="/public/local" active-class="text-primary" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop"> <NuxtLink group to="/public/local" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
<div i-ri:group-2-line /> <div i-ri:group-2-line />
</NuxtLink> </NuxtLink>
<NuxtLink to="/public" active-class="text-primary" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop"> <template v-if="!currentUser">
<NuxtLink to="/public" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
<div i-ri:earth-line /> <div i-ri:earth-line />
</NuxtLink> </NuxtLink>
</template>
<template v-if="currentUser"> <template v-if="currentUser">
<NuxtLink to="/conversations" active-class="text-primary" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop"> <NuxtLink to="/conversations" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 @click="$scrollToTop">
<div i-ri:at-line /> <div i-ri:at-line />
</NuxtLink> </NuxtLink>
</template> </template>
<NavBottomMoreMenu v-slot="{ changeShow, show }" v-model="moreMenuVisible" flex flex-row items-center place-content-center h-full flex-1 cursor-pointer>
<label
flex items-center place-content-center h-full flex-1 class="selete-none"
:class="show ? '!text-primary' : ''"
>
<input type="checkbox" z="-1" absolute inset-0 opacity-0 @click="changeShow">
<span v-show="show" i-ri:close-fill />
<span v-show="!show" i-ri:more-fill />
</label>
</NavBottomMoreMenu>
</nav> </nav>
</template> </template>
<style scoped>
.scrollbar-hide {
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
</style>

View file

@ -0,0 +1,143 @@
<script lang="ts" setup>
import type { ComputedRef } from 'vue'
import type { LocaleObject } from '#i18n'
const props = defineProps<{
modelValue?: boolean
}>()
const emits = defineEmits<{
(event: 'update:modelValue', value: boolean): void
}>()
const visible = useVModel(props, 'modelValue', emits, { passive: true })
const { t, locale, setLocale } = useI18n()
const { locales } = useI18n() as { locales: ComputedRef<LocaleObject[]> }
const toggleLocales = () => {
const codes = locales.value.map(item => item.code)
setLocale(codes[(codes.indexOf(locale.value) + 1) % codes.length])
}
function changeShow() {
visible.value = !visible.value
}
const buttonEl = ref<HTMLDivElement>()
/** Close the drop-down menu if the mouse click is not on the drop-down menu button when the drop-down menu is opened */
function clickEvent(mouse: MouseEvent) {
if (mouse.target && !buttonEl.value?.children[0].contains(mouse.target as any)) {
if (visible.value) {
document.removeEventListener('click', clickEvent)
visible.value = false
}
}
}
watch(visible, (val, oldVal) => {
if (val && val !== oldVal) {
if (!import.meta.env.SSR && typeof document !== 'undefined')
document.addEventListener('click', clickEvent)
}
}, { flush: 'post' })
onBeforeUnmount(() => {
if (!import.meta.env.SSR)
document.removeEventListener('click', clickEvent)
})
</script>
<template>
<div ref="buttonEl" flex items-center static>
<slot :change-show="changeShow" :show="visible" />
<!-- Drawer -->
<Transition
enter-active-class="transition duration-250 ease-out children:(transition duration-250 ease-out)"
enter-from-class="opacity-0 children:(transform translate-y-full)"
enter-to-class="opacity-100 children:(transform translate-y-0)"
leave-active-class="transition duration-250 ease-in children:(transition duration-250 ease-in)"
leave-from-class="opacity-100 children:(transform translate-y-0)"
leave-to-class="opacity-0 children:(transform translate-y-full)"
persisted
>
<div
v-show="visible"
class="scrollbar-hide"
absolute inset-x-0 top-auto bottom-full z-20 h-100vh
flex items-end of-y-scroll of-x-hidden overscroll-none
bg="black/50"
>
<!-- The style `scrollbar-hide overscroll-none overflow-y-scroll mb="-1px"` and `h="[calc(100%+0.5px)]"` is used to implement scroll locking, -->
<!-- corresponding to issue: #106, so please don't remove it. -->
<div absolute inset-0 opacity-0 h="[calc(100vh+0.5px)]" />
<div
class="scrollbar-hide"
flex-1 min-w-48 py-6 mb="-1px"
overflow-y-auto overscroll-none max-h="[calc(100vh-200px)]"
rounded-t-lg bg="white/85 dark:neutral-900/85" backdrop-filter backdrop-blur-md
border-t-1 border-base
>
<!-- Nav -->
<NavSide />
<!-- Divider line -->
<div border="neutral-300 dark:neutral-700 t-1" m="x-3 y-2" />
<!-- Function menu -->
<div flex="~ col gap2">
<!-- Toggle Theme -->
<button
flex flex-row items-center
block px-5 py-2 focus-blue w-full
text-sm text-base capitalize text-left whitespace-nowrap
transition-colors duration-200 transform
hover="bg-gray-100 dark:(bg-gray-700 text-white)"
@click="toggleDark()"
>
<span class="i-ri:sun-line dark:i-ri:moon-line flex-shrink-0 text-xl mr-4 !align-middle" />
{{ !isDark ? t('menu.toggle_theme.dark') : t('menu.toggle_theme.light') }}
</button>
<!-- Switch languages -->
<NavSelectLanguage>
<button
flex flex-row items-center
block px-5 py-2 focus-blue w-full
text-sm text-base capitalize text-left whitespace-nowrap
transition-colors duration-200 transform
hover="bg-gray-100 dark:(bg-gray-700 text-white)"
@click.stop
>
<span class="i-ri:earth-line flex-shrink-0 text-xl mr-4 !align-middle" />
{{ $t('nav_footer.select_language') }}
</button>
</NavSelectLanguage>
<!-- Toggle Feature Flags -->
<NavSelectFeatureFlags v-if="currentUser">
<button
flex flex-row items-center
block px-5 py-2 focus-blue w-full
text-sm text-base capitalize text-left whitespace-nowrap
transition-colors duration-200 transform
hover="bg-gray-100 dark:(bg-gray-700 text-white)"
@click.stop
>
<span class="i-ri:flag-line flex-shrink-0 text-xl mr-4 !align-middle" />
{{ $t('nav_footer.select_feature_flags') }}
</button>
</NavSelectFeatureFlags>
</div>
</div>
</div>
</Transition>
</div>
</template>
<style scoped>
.scrollbar-hide {
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
</style>

View file

@ -22,8 +22,20 @@ const buildTimeAgo = useTimeAgo(buildTime, timeAgoOptions)
@click="toggleZenMode()" @click="toggleZenMode()"
/> />
</CommonTooltip> </CommonTooltip>
<NavSelectLanguage /> <NavSelectLanguage>
<NavSelectFeatureFlags v-if="currentUser" /> <CommonTooltip :content="$t('nav_footer.select_language')">
<button flex :aria-label="$t('nav_footer.select_language')">
<div i-ri:earth-line text-lg />
</button>
</CommonTooltip>
</NavSelectLanguage>
<NavSelectFeatureFlags v-if="currentUser">
<CommonTooltip :content="$t('nav_footer.select_feature_flags')">
<button flex :aria-label="$t('nav_footer.select_feature_flags')">
<div i-ri:flag-line text-lg />
</button>
</CommonTooltip>
</NavSelectFeatureFlags>
</div> </div>
<div> <div>
<button cursor-pointer hover:underline @click="openPreviewHelp"> <button cursor-pointer hover:underline @click="openPreviewHelp">

View file

@ -3,13 +3,13 @@ const { notifications } = useNotifications()
</script> </script>
<template> <template>
<nav px3 py4 flex="~ col gap2" text-lg> <nav md:px3 md:py4 flex="~ col gap2" text-size-base leading-normal md:text-lg>
<template v-if="currentUser"> <template v-if="currentUser">
<NavSideItem :text="$t('nav_side.home')" to="/home" icon="i-ri:home-5-line" /> <NavSideItem :text="$t('nav_side.home')" to="/home" icon="i-ri:home-5-line" />
<NavSideItem :text="$t('nav_side.notifications')" to="/notifications" icon="i-ri:notification-4-line"> <NavSideItem :text="$t('nav_side.notifications')" to="/notifications" icon="i-ri:notification-4-line">
<template #icon> <template #icon>
<div flex relative> <div flex relative>
<div class="i-ri:notification-4-line" /> <div class="i-ri:notification-4-line" md:text-size-inherit text-xl />
<div v-if="notifications" class="top-[-0.3rem] right-[-0.3rem]" absolute font-bold rounded-full h-4 w-4 text-xs bg-primary text-inverted flex items-center justify-center> <div v-if="notifications" class="top-[-0.3rem] right-[-0.3rem]" absolute font-bold rounded-full h-4 w-4 text-xs bg-primary text-inverted flex items-center justify-center>
{{ notifications < 10 ? notifications : '•' }} {{ notifications < 10 ? notifications : '•' }}
</div> </div>
@ -31,7 +31,7 @@ const { notifications } = useNotifications()
icon="i-ri:account-circle-line" icon="i-ri:account-circle-line"
> >
<template #icon> <template #icon>
<AccountAvatar :account="currentUser.account" h="1.2em" /> <AccountAvatar :account="currentUser.account" h="1.2em" md:text-size-inherit text-xl />
</template> </template>
<ContentRich <ContentRich
:content="getDisplayName(currentUser.account, { rich: true }) || $t('nav_side.profile')" :content="getDisplayName(currentUser.account, { rich: true }) || $t('nav_side.profile')"

View file

@ -26,9 +26,9 @@ useCommand({
<template> <template>
<NuxtLink :to="to" active-class="text-primary" group focus:outline-none @click="$scrollToTop"> <NuxtLink :to="to" active-class="text-primary" group focus:outline-none @click="$scrollToTop">
<div flex w-fit px5 py2 gap2 items-center transition-100 rounded-full group-hover:bg-active group-focus-visible:ring="2 current"> <div flex w-fit px5 py2 md:gap2 gap4 items-center transition-100 rounded-full group-hover:bg-active group-focus-visible:ring="2 current">
<slot name="icon"> <slot name="icon">
<div :class="icon" /> <div :class="icon" md:text-size-inherit text-xl />
</slot> </slot>
<slot> <slot>
<span>{{ text }}</span> <span>{{ text }}</span>

View file

@ -1,24 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
const { t } = useI18n()
const featureFlags = useFeatureFlags() const featureFlags = useFeatureFlags()
</script> </script>
<template> <template>
<CommonTooltip :content="t('nav_footer.select_feature_flags')"> <CommonDropdown placement="top">
<CommonDropdown> <slot />
<button flex :aria-label="t('nav_footer.select_feature_flags')">
<div i-ri:flag-line text-lg />
</button>
<template #popper> <template #popper>
<CommonDropdownItem <CommonDropdownItem
:checked="featureFlags.experimentalVirtualScroll" :checked="featureFlags.experimentalVirtualScroll"
@click="toggleFeatureFlag('experimentalVirtualScroll')" @click="toggleFeatureFlag('experimentalVirtualScroll')"
> >
{{ t('feature_flag.virtual_scroll') }} {{ $t('feature_flag.virtual_scroll') }}
</CommonDropdownItem> </CommonDropdownItem>
</template> </template>
</CommonDropdown> </CommonDropdown>
</CommonTooltip>
</template> </template>

View file

@ -7,11 +7,8 @@ const { locales } = useI18n() as { locales: ComputedRef<LocaleObject[]> }
</script> </script>
<template> <template>
<CommonTooltip :content="t('nav_footer.select_language')">
<CommonDropdown> <CommonDropdown>
<button flex :aria-label="t('nav_footer.select_language')"> <slot />
<div i-ri:earth-line text-lg />
</button>
<template #popper> <template #popper>
<CommonDropdownItem <CommonDropdownItem
@ -24,5 +21,4 @@ const { locales } = useI18n() as { locales: ComputedRef<LocaleObject[]> }
</CommonDropdownItem> </CommonDropdownItem>
</template> </template>
</CommonDropdown> </CommonDropdown>
</CommonTooltip>
</template> </template>

View file

@ -13,7 +13,7 @@
</slot> </slot>
</div> </div>
</aside> </aside>
<div class="w-full mb14 md:(w-3/4 mb0) lg:(w-2/4 mb0) min-h-screen" border="l r base"> <div class="w-full mb14 md:(w-3/4 mb0) lg:(w-2/4 mb0) min-h-screen" border="none md:l md:r base">
<div min-h-screen> <div min-h-screen>
<slot /> <slot />
</div> </div>

View file

@ -81,6 +81,10 @@
"open_in_original_site": "Open in original site", "open_in_original_site": "Open in original site",
"pin_on_profile": "Pin on profile", "pin_on_profile": "Pin on profile",
"show_untranslated": "Show untranslated", "show_untranslated": "Show untranslated",
"toggle_theme": {
"dark": "Toggle dark mode",
"light": "Toggle light mode"
},
"translate_post": "Translate post", "translate_post": "Translate post",
"unblock_account": "Unblock {0}", "unblock_account": "Unblock {0}",
"unblock_domain": "Unblock domain {0}", "unblock_domain": "Unblock domain {0}",

View file

@ -81,6 +81,10 @@
"open_in_original_site": "从源站打开", "open_in_original_site": "从源站打开",
"pin_on_profile": "钉选在个人资料上", "pin_on_profile": "钉选在个人资料上",
"show_untranslated": "显示原文", "show_untranslated": "显示原文",
"toggle_theme": {
"dark": "切换深色模式",
"light": "切换亮色模式"
},
"translate_post": "翻译帖子", "translate_post": "翻译帖子",
"unblock_account": "解除拉黑 {0}", "unblock_account": "解除拉黑 {0}",
"unblock_domain": "解除拉黑域名 {0}", "unblock_domain": "解除拉黑域名 {0}",