1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-14 06:25:52 +02:00

enhance(frontend): improve and refactor mobile nav footer

This commit is contained in:
syuilo
2025-04-07 19:45:28 +09:00
parent 65b4458474
commit 33e6ebb2ee
4 changed files with 225 additions and 180 deletions

View File

@@ -0,0 +1,144 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div ref="rootEl" :class="$style.root">
<button :class="$style.item" class="_button" @click="drawerMenuShowing = true">
<div :class="$style.itemInner">
<i :class="$style.itemIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.itemIndicator" class="_blink"><i class="_indicatorCircle"></i></span>
</div>
</button>
<button :class="$style.item" class="_button" @click="mainRouter.push('/')">
<div :class="$style.itemInner">
<i :class="$style.itemIcon" class="ti ti-home"></i>
</div>
</button>
<button :class="$style.item" class="_button" @click="mainRouter.push('/my/notifications')">
<div :class="$style.itemInner">
<i :class="$style.itemIcon" class="ti ti-bell"></i>
<span v-if="$i?.hasUnreadNotification" :class="$style.itemIndicator" class="_blink">
<span class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ $i.unreadNotificationsCount > 99 ? '99+' : $i.unreadNotificationsCount }}</span>
</span>
</div>
</button>
<button :class="$style.item" class="_button" @click="widgetsShowing = true">
<div :class="$style.itemInner">
<i :class="$style.itemIcon" class="ti ti-apps"></i>
</div>
</button>
<button :class="[$style.item, $style.post]" class="_button" @click="os.post()">
<div :class="$style.itemInner">
<i :class="$style.itemIcon" class="ti ti-pencil"></i>
</div>
</button>
</div>
</template>
<script lang="ts" setup>
import { computed, ref, useTemplateRef, watch } from 'vue';
import { $i } from '@/i.js';
import * as os from '@/os.js';
import { mainRouter } from '@/router.js';
import { navbarItemDef } from '@/navbar.js';
const drawerMenuShowing = defineModel<boolean>('drawerMenuShowing');
const widgetsShowing = defineModel<boolean>('widgetsShowing');
const rootEl = useTemplateRef('rootEl');
const menuIndicated = computed(() => {
for (const def in navbarItemDef) {
if (def === 'notifications') continue; // 通知は下にボタンとして表示されてるから
if (navbarItemDef[def].indicated) return true;
}
return false;
});
const rootElHeight = ref(0);
watch(rootEl, () => {
if (rootEl.value) {
rootElHeight.value = rootEl.value.offsetHeight;
window.document.body.style.setProperty('--MI-minBottomSpacing', 'var(--MI-minBottomSpacingMobile)');
} else {
rootElHeight.value = 0;
window.document.body.style.setProperty('--MI-minBottomSpacing', '0px');
}
}, {
immediate: true,
});
</script>
<style lang="scss" module>
.root {
padding: 12px 12px max(12px, env(safe-area-inset-bottom, 0px)) 12px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
grid-gap: 8px;
width: 100%;
box-sizing: border-box;
background: var(--MI_THEME-bg);
border-top: solid 0.5px var(--MI_THEME-divider);
}
.item {
&.post {
.itemInner {
background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
color: var(--MI_THEME-fgOnAccent);
&:hover {
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
}
&:active {
background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
}
}
}
}
.itemInner {
position: relative;
padding: 0;
aspect-ratio: 1;
width: 100%;
max-width: 50px;
margin: auto;
align-content: center;
border-radius: 100%;
background: var(--MI_THEME-panel);
color: var(--MI_THEME-fg);
&:hover {
background: var(--MI_THEME-panelHighlight);
}
&:active {
background: hsl(from var(--MI_THEME-panel) h s calc(l - 2));
}
}
.itemIcon {
font-size: 14px;
}
.itemIndicator {
position: absolute;
top: 0;
left: 0;
color: var(--MI_THEME-indicator);
font-size: 16px;
&:has(.itemIndicateValueIcon) {
animation: none;
font-size: 12px;
}
}
</style>

View File

@@ -0,0 +1,81 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div>
<XWidgets :edit="editMode" :widgets="widgets" @addWidget="addWidget" @removeWidget="removeWidget" @updateWidget="updateWidget" @updateWidgets="updateWidgets" @exit="editMode = false"/>
<button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="ti ti-check"></i> {{ i18n.ts.editWidgetsExit }}</button>
<button v-else class="_textButton" data-cy-widget-edit :class="$style.edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button>
</div>
</template>
<script lang="ts">
import { computed, ref } from 'vue';
const editMode = ref(false);
</script>
<script lang="ts" setup>
import XWidgets from '@/components/MkWidgets.vue';
import { i18n } from '@/i18n.js';
import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
// null = 全てのウィジェットを表示
// left = place: leftだけを表示
// right = rightとnullを表示
place?: 'left' | null | 'right';
}>(), {
place: null,
});
const widgets = computed(() => {
if (props.place === null) return prefer.r.widgets.value;
if (props.place === 'left') return prefer.r.widgets.value.filter(w => w.place === 'left');
return prefer.r.widgets.value.filter(w => w.place !== 'left');
});
function addWidget(widget) {
prefer.commit('widgets', [{
...widget,
place: props.place,
}, ...prefer.s.widgets]);
}
function removeWidget(widget) {
prefer.commit('widgets', prefer.s.widgets.filter(w => w.id !== widget.id));
}
function updateWidget({ id, data }) {
prefer.commit('widgets', prefer.s.widgets.map(w => w.id === id ? {
...w,
data,
place: props.place,
} : w));
}
function updateWidgets(thisWidgets) {
if (props.place === null) {
prefer.commit('widgets', thisWidgets);
return;
}
if (props.place === 'left') {
prefer.commit('widgets', [
...thisWidgets.map(w => ({ ...w, place: 'left' })),
...prefer.s.widgets.filter(w => w.place !== 'left' && !thisWidgets.some(t => w.id === t.id)),
]);
return;
}
prefer.commit('widgets', [
...prefer.s.widgets.filter(w => w.place === 'left' && !thisWidgets.some(t => w.id === t.id)),
...thisWidgets.map(w => ({ ...w, place: 'right' })),
]);
}
</script>
<style lang="scss" module>
.edit {
width: 100%;
}
</style>