diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-04-07 19:45:28 +0900 |
|---|---|---|
| committer | syuilo <4439005+syuilo@users.noreply.github.com> | 2025-04-07 19:45:28 +0900 |
| commit | 33e6ebb2ee1b66bcf325286ebc742bb0a8d4ae36 (patch) | |
| tree | 3a54952e359c8fc919a6b7b17f8739710605190e /packages/frontend/src/ui/_common_ | |
| parent | fix(frontend): fix drawer menu style (diff) | |
| download | sharkey-33e6ebb2ee1b66bcf325286ebc742bb0a8d4ae36.tar.gz sharkey-33e6ebb2ee1b66bcf325286ebc742bb0a8d4ae36.tar.bz2 sharkey-33e6ebb2ee1b66bcf325286ebc742bb0a8d4ae36.zip | |
enhance(frontend): improve and refactor mobile nav footer
Diffstat (limited to 'packages/frontend/src/ui/_common_')
| -rw-r--r-- | packages/frontend/src/ui/_common_/mobile-footer-menu.vue | 144 | ||||
| -rw-r--r-- | packages/frontend/src/ui/_common_/widgets.vue | 81 |
2 files changed, 225 insertions, 0 deletions
diff --git a/packages/frontend/src/ui/_common_/mobile-footer-menu.vue b/packages/frontend/src/ui/_common_/mobile-footer-menu.vue new file mode 100644 index 0000000000..37b70847ca --- /dev/null +++ b/packages/frontend/src/ui/_common_/mobile-footer-menu.vue @@ -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> diff --git a/packages/frontend/src/ui/_common_/widgets.vue b/packages/frontend/src/ui/_common_/widgets.vue new file mode 100644 index 0000000000..1a6d62e19b --- /dev/null +++ b/packages/frontend/src/ui/_common_/widgets.vue @@ -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> |