summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-02-12 11:20:17 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2023-02-12 11:20:17 +0900
commitc5ef6bf38ada420e80091891f57beeaa6b87b68c (patch)
treed2f2f7b3c4586df1ab1af0cf61af2ca5571810cc /packages/frontend/src/components
parentMerge branch 'develop' (diff)
parent13.6.1 (diff)
downloadmisskey-c5ef6bf38ada420e80091891f57beeaa6b87b68c.tar.gz
misskey-c5ef6bf38ada420e80091891f57beeaa6b87b68c.tar.bz2
misskey-c5ef6bf38ada420e80091891f57beeaa6b87b68c.zip
Merge branch 'develop'
Diffstat (limited to 'packages/frontend/src/components')
-rw-r--r--packages/frontend/src/components/MkNote.vue42
-rw-r--r--packages/frontend/src/components/MkNoteDetailed.vue32
-rw-r--r--packages/frontend/src/components/MkNoteSimple.vue12
-rw-r--r--packages/frontend/src/components/MkPagination.vue2
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.tabs.vue234
-rw-r--r--packages/frontend/src/components/global/MkPageHeader.vue233
6 files changed, 335 insertions, 220 deletions
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index e910fbab01..c9c512c36e 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -78,7 +78,7 @@
</div>
<footer :class="$style.footer">
<MkReactionsViewer :note="appearNote" :max-number="16">
- <template v-slot:more>
+ <template #more>
<button class="_button" :class="$style.reactionDetailsButton" @click="showReactions">
{{ i18n.ts.more }}
</button>
@@ -156,6 +156,7 @@ import { useTooltip } from '@/scripts/use-tooltip';
import { claimAchievement } from '@/scripts/achievements';
import { getNoteSummary } from '@/scripts/get-note-summary';
import { shownNoteIds } from '@/os';
+import { MenuItem } from '@/types/menu';
const props = defineProps<{
note: misskey.entities.Note;
@@ -206,7 +207,7 @@ const translation = ref<any>(null);
const translating = ref(false);
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id);
-let renoteCollapsed = $ref(isRenote && (($i && ($i.id === note.userId)) || shownNoteIds.has(appearNote.id)));
+let renoteCollapsed = $ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.userId)) || shownNoteIds.has(appearNote.id)));
shownNoteIds.add(appearNote.id);
@@ -247,7 +248,32 @@ useTooltip(renoteButton, async (showing) => {
function renote(viaKeyboard = false) {
pleaseLogin();
- os.popupMenu([{
+
+ let items = [] as MenuItem[];
+
+ if (appearNote.channel) {
+ items = items.concat([{
+ text: i18n.ts.inChannelRenote,
+ icon: 'ti ti-repeat',
+ action: () => {
+ os.api('notes/create', {
+ renoteId: appearNote.id,
+ channelId: appearNote.channelId,
+ });
+ },
+ }, {
+ text: i18n.ts.inChannelQuote,
+ icon: 'ti ti-quote',
+ action: () => {
+ os.post({
+ renote: appearNote,
+ channel: appearNote.channel,
+ });
+ },
+ }, null]);
+ }
+
+ items = items.concat([{
text: i18n.ts.renote,
icon: 'ti ti-repeat',
action: () => {
@@ -263,7 +289,9 @@ function renote(viaKeyboard = false) {
renote: appearNote,
});
},
- }], renoteButton.value, {
+ }]);
+
+ os.popupMenu(items, renoteButton.value, {
viaKeyboard,
});
}
@@ -704,6 +732,12 @@ function showReactions(): void {
}
}
+@container (max-width: 250px) {
+ .quoteNote {
+ padding: 12px;
+ }
+}
+
.muted {
padding: 8px;
text-align: center;
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 0da06c4f14..92bdadc562 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -160,6 +160,7 @@ import { useNoteCapture } from '@/scripts/use-note-capture';
import { deepClone } from '@/scripts/clone';
import { useTooltip } from '@/scripts/use-tooltip';
import { claimAchievement } from '@/scripts/achievements';
+import { MenuItem } from '@/types/menu';
const props = defineProps<{
note: misskey.entities.Note;
@@ -241,7 +242,32 @@ useTooltip(renoteButton, async (showing) => {
function renote(viaKeyboard = false) {
pleaseLogin();
- os.popupMenu([{
+
+ let items = [] as MenuItem[];
+
+ if (appearNote.channel) {
+ items = items.concat([{
+ text: i18n.ts.inChannelRenote,
+ icon: 'ti ti-repeat',
+ action: () => {
+ os.api('notes/create', {
+ renoteId: appearNote.id,
+ channelId: appearNote.channelId,
+ });
+ },
+ }, {
+ text: i18n.ts.inChannelQuote,
+ icon: 'ti ti-quote',
+ action: () => {
+ os.post({
+ renote: appearNote,
+ channel: appearNote.channel,
+ });
+ },
+ }, null]);
+ }
+
+ items = items.concat([{
text: i18n.ts.renote,
icon: 'ti ti-repeat',
action: () => {
@@ -257,7 +283,9 @@ function renote(viaKeyboard = false) {
renote: appearNote,
});
},
- }], renoteButton.value, {
+ }]);
+
+ os.popupMenu(items, renoteButton.value, {
viaKeyboard,
});
}
diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue
index 757b325a06..b38a4afa8b 100644
--- a/packages/frontend/src/components/MkNoteSimple.vue
+++ b/packages/frontend/src/components/MkNoteSimple.vue
@@ -44,8 +44,8 @@ const showContent = $ref(false);
flex-shrink: 0;
display: block;
margin: 0 10px 0 0;
- width: 40px;
- height: 40px;
+ width: 34px;
+ height: 34px;
border-radius: 8px;
}
@@ -72,6 +72,14 @@ const showContent = $ref(false);
padding: 0;
}
+@container (min-width: 250px) {
+ .avatar {
+ margin: 0 10px 0 0;
+ width: 40px;
+ height: 40px;
+ }
+}
+
@container (min-width: 350px) {
.avatar {
margin: 0 10px 0 0;
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index 04c8616c9a..224a42cdc2 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -21,7 +21,7 @@
<div v-else ref="rootEl">
<div v-show="pagination.reversed && more" key="_more_" class="_margin">
- <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? fetchMore : null" :class="$style.more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
+ <MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? fetchMoreAhead : null" :class="$style.more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead">
{{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-else class="loading"/>
diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
new file mode 100644
index 0000000000..dae68c7e9c
--- /dev/null
+++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
@@ -0,0 +1,234 @@
+<template>
+ <div ref="el" :class="$style.tabs" @wheel="onTabWheel">
+ <div :class="$style.tabsInner">
+ <button v-for="t in tabs" :ref="(el) => tabRefs[t.key] = (el as HTMLElement)" v-tooltip.noDelay="t.title"
+ class="_button" :class="[$style.tab, { [$style.active]: t.key != null && t.key === props.tab, [$style.animate]: defaultStore.reactiveState.animation.value }]"
+ @mousedown="(ev) => onTabMousedown(t, ev)" @click="(ev) => onTabClick(t, ev)">
+ <div :class="$style.tabInner">
+ <i v-if="t.icon" :class="[$style.tabIcon, t.icon]"></i>
+ <div v-if="!t.iconOnly || (!defaultStore.reactiveState.animation.value && t.key === tab)"
+ :class="$style.tabTitle">{{ t.title }}</div>
+ <Transition v-else @enter="enter" @after-enter="afterEnter" @leave="leave" @after-leave="afterLeave"
+ mode="in-out">
+ <div v-show="t.key === tab" :class="[$style.tabTitle, $style.animate]">{{ t.title }}</div>
+ </Transition>
+ </div>
+ </button>
+ </div>
+ <div ref="tabHighlightEl"
+ :class="[$style.tabHighlight, { [$style.animate]: defaultStore.reactiveState.animation.value }]"></div>
+ </div>
+</template>
+
+<script lang="ts">
+export type Tab = {
+ key: string;
+ title: string;
+ icon?: string;
+ iconOnly?: boolean;
+ onClick?: (ev: MouseEvent) => void;
+} & {
+ iconOnly: true;
+ iccn: string;
+};
+</script>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted, watch, nextTick, Transition, shallowRef } from 'vue';
+import { defaultStore } from '@/store';
+
+const props = withDefaults(defineProps<{
+ tabs?: Tab[];
+ tab?: string;
+ rootEl?: HTMLElement;
+}>(), {
+ tabs: () => ([] as Tab[]),
+});
+
+const emit = defineEmits<{
+ (ev: 'update:tab', key: string);
+ (ev: 'tabClick', key: string);
+}>();
+
+const el = shallowRef<HTMLElement | null>(null);
+const tabRefs: Record<string, HTMLElement | null> = {};
+const tabHighlightEl = shallowRef<HTMLElement | null>(null);
+
+function onTabMousedown(tab: Tab, ev: MouseEvent): void {
+ // ユーザビリティの観点からmousedown時にはonClickは呼ばない
+ if (tab.key) {
+ emit('update:tab', tab.key);
+ }
+}
+
+function onTabClick(t: Tab, ev: MouseEvent): void {
+ emit('tabClick', t.key);
+
+ if (t.onClick) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ t.onClick(ev);
+ }
+
+ if (t.key) {
+ emit('update:tab', t.key);
+ }
+}
+
+function renderTab() {
+ const tabEl = props.tab ? tabRefs[props.tab] : undefined;
+ if (tabEl && tabHighlightEl.value && tabHighlightEl.value.parentElement) {
+ // offsetWidth や offsetLeft は少数を丸めてしまうため getBoundingClientRect を使う必要がある
+ // https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetWidth#%E5%80%A4
+ const parentRect = tabHighlightEl.value.parentElement.getBoundingClientRect();
+ const rect = tabEl.getBoundingClientRect();
+ tabHighlightEl.value.style.width = rect.width + 'px';
+ tabHighlightEl.value.style.left = (rect.left - parentRect.left + tabHighlightEl.value.parentElement.scrollLeft) + 'px';
+ }
+}
+
+function onTabWheel(ev: WheelEvent) {
+ if (ev.deltaY !== 0 && ev.deltaX === 0) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ (ev.currentTarget as HTMLElement).scrollBy({
+ left: ev.deltaY,
+ behavior: 'smooth',
+ });
+ }
+ return false;
+}
+
+let entering = false;
+
+async function enter(el: HTMLElement) {
+ entering = true;
+ const elementWidth = el.getBoundingClientRect().width;
+ el.style.width = '0';
+ el.style.paddingLeft = '0';
+ el.offsetWidth; // force reflow
+ el.style.width = elementWidth + 'px';
+ el.style.paddingLeft = '';
+ nextTick(() => {
+ entering = false;
+ });
+
+ setTimeout(renderTab, 170);
+}
+function afterEnter(el: HTMLElement) {
+ //el.style.width = '';
+}
+async function leave(el: HTMLElement) {
+ const elementWidth = el.getBoundingClientRect().width;
+ el.style.width = elementWidth + 'px';
+ el.style.paddingLeft = '';
+ el.offsetWidth; // force reflow
+ el.style.width = '0';
+ el.style.paddingLeft = '0';
+}
+function afterLeave(el: HTMLElement) {
+ el.style.width = '';
+}
+
+let ro2: ResizeObserver | null;
+
+onMounted(() => {
+ watch([() => props.tab, () => props.tabs], () => {
+ nextTick(() => {
+ if (entering) return;
+ renderTab();
+ });
+ }, {
+ immediate: true,
+ });
+
+ if (props.rootEl) {
+ ro2 = new ResizeObserver((entries, observer) => {
+ if (document.body.contains(el.value as HTMLElement)) {
+ nextTick(() => renderTab());
+ }
+ });
+ ro2.observe(props.rootEl);
+ }
+});
+
+onUnmounted(() => {
+ if (ro2) ro2.disconnect();
+});
+</script>
+
+<style lang="scss" module>
+.tabs {
+ display: block;
+ position: relative;
+ margin: 0;
+ height: var(--height);
+ font-size: 0.8em;
+ text-align: center;
+ overflow-x: auto;
+ overflow-y: hidden;
+ scrollbar-width: none;
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+}
+
+.tabsInner {
+ display: inline-block;
+ height: var(--height);
+ white-space: nowrap;
+}
+
+.tab {
+ display: inline-block;
+ position: relative;
+ padding: 0 10px;
+ height: 100%;
+ font-weight: normal;
+ opacity: 0.7;
+
+ &:hover {
+ opacity: 1;
+ }
+
+ &.active {
+ opacity: 1;
+ }
+
+ &.animate {
+ transition: opacity 0.2s ease;
+ }
+}
+
+.tabInner {
+ display: flex;
+ align-items: center;
+}
+
+.tabIcon+.tabTitle {
+ padding-left: 8px;
+}
+
+.tabTitle {
+ overflow: hidden;
+
+ &.animate {
+ transition: width .15s linear, padding-left .15s linear;
+ }
+}
+
+.tabHighlight {
+ position: absolute;
+ bottom: 0;
+ height: 3px;
+ background: var(--accent);
+ border-radius: 999px;
+ transition: none;
+ pointer-events: none;
+
+ &.animate {
+ transition: width 0.15s ease, left 0.15s ease;
+ }
+}
+</style>
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index 23a39b9ac9..803efb1690 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -1,10 +1,10 @@
<template>
<div v-if="show" ref="el" :class="[$style.root]" :style="{ background: bg }">
<div :class="[$style.upper, { [$style.slim]: narrow, [$style.thin]: thin_ }]">
- <div v-if="narrow && props.displayMyAvatar && $i" class="_button" :class="$style.buttonsLeft" @click="openAccountMenu">
+ <div v-if="!thin_ && narrow && props.displayMyAvatar && $i" class="_button" :class="$style.buttonsLeft" @click="openAccountMenu">
<MkAvatar :class="$style.avatar" :user="$i" />
</div>
- <div v-else-if="narrow && !hideTitle" :class="$style.buttonsLeft" />
+ <div v-else-if="!thin_ && narrow && !hideTitle" :class="$style.buttonsLeft" />
<template v-if="metadata">
<div v-if="!hideTitle" :class="$style.titleContainer" @click="top">
@@ -19,63 +19,28 @@
</div>
</div>
</div>
- <div v-if="!narrow || hideTitle" :class="$style.tabs" @wheel="onTabWheel">
- <div :class="$style.tabsInner">
- <button v-for="t in tabs" :ref="(el) => tabRefs[t.key] = (el as HTMLElement)" v-tooltip.noDelay="t.title" class="_button" :class="[$style.tab, { [$style.active]: t.key != null && t.key === props.tab }]" @mousedown="(ev) => onTabMousedown(t, ev)" @click="(ev) => onTabClick(t, ev)">
- <div :class="$style.tabInner">
- <i v-if="t.icon" :class="[$style.tabIcon, t.icon]"></i>
- <div v-if="!t.iconOnly" :class="$style.tabTitle">{{ t.title }}</div>
- <Transition
- v-else
- @enter="enter"
- @after-enter="afterEnter"
- @leave="leave"
- @after-leave="afterLeave"
- mode="in-out"
- >
- <div v-if="t.key === tab" :class="$style.tabTitle">{{ t.title }}</div>
- </Transition>
- </div>
- </button>
- </div>
- <div ref="tabHighlightEl" :class="$style.tabHighlight"></div>
- </div>
+ <XTabs v-if="!narrow || hideTitle" :class="$style.tabs" :tab="tab" @update:tab="key => emit('update:tab', key)" :tabs="tabs" :root-el="el" @tab-click="onTabClick"/>
</template>
- <div v-if="(narrow && !hideTitle) || (actions && actions.length > 0)" :class="$style.buttonsRight">
+ <div v-if="(!thin_ && narrow && !hideTitle) || (actions && actions.length > 0)" :class="$style.buttonsRight">
<template v-for="action in actions">
<button v-tooltip.noDelay="action.text" class="_button" :class="[$style.button, { [$style.highlighted]: action.highlighted }]" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button>
</template>
</div>
</div>
<div v-if="(narrow && !hideTitle) && hasTabs" :class="[$style.lower, { [$style.slim]: narrow, [$style.thin]: thin_ }]">
- <div :class="$style.tabs" @wheel="onTabWheel">
- <div :class="$style.tabsInner">
- <button v-for="tab in tabs" :ref="(el) => tabRefs[tab.key] = (el as HTMLElement)" v-tooltip.noDelay="tab.title" class="_button" :class="[$style.tab, { [$style.active]: tab.key != null && tab.key === props.tab }]" @mousedown="(ev) => onTabMousedown(tab, ev)" @click="(ev) => onTabClick(tab, ev)">
- <i v-if="tab.icon" :class="[$style.tabIcon, tab.icon]"></i>
- <span v-if="!tab.iconOnly" :class="$style.tabTitle">{{ tab.title }}</span>
- </button>
- </div>
- <div ref="tabHighlightEl" :class="$style.tabHighlight"></div>
- </div>
+ <XTabs :class="$style.tabs" :tab="tab" @update:tab="key => emit('update:tab', key)" :tabs="tabs" :root-el="el" @tab-click="onTabClick"/>
</div>
</div>
</template>
<script lang="ts" setup>
-import { onMounted, onUnmounted, ref, inject, watch, nextTick } from 'vue';
+import { onMounted, onUnmounted, ref, inject } from 'vue';
import tinycolor from 'tinycolor2';
import { scrollToTop } from '@/scripts/scroll';
import { globalEvents } from '@/events';
import { injectPageMetadata } from '@/scripts/page-metadata';
import { $i, openAccountMenu as openAccountMenu_ } from '@/account';
-
-type Tab = {
- key: string;
- title: string;
- icon?: string;
- iconOnly?: boolean;
- onClick?: (ev: MouseEvent) => void;
-};
+import XTabs, { Tab } from './MkPageHeader.tabs.vue'
const props = withDefaults(defineProps<{
tabs?: Tab[];
@@ -102,8 +67,6 @@ const hideTitle = inject('shouldOmitHeaderTitle', false);
const thin_ = props.thin || inject('shouldHeaderThin', false);
let el = $shallowRef<HTMLElement | undefined>(undefined);
-const tabRefs: Record<string, HTMLElement | null> = {};
-let tabHighlightEl = $shallowRef<HTMLElement | null>(null);
const bg = ref<string | undefined>(undefined);
let narrow = $ref(false);
const hasTabs = $computed(() => props.tabs.length > 0);
@@ -128,25 +91,8 @@ function openAccountMenu(ev: MouseEvent) {
}, ev);
}
-function onTabMousedown(tab: Tab, ev: MouseEvent): void {
- // ユーザビリティの観点からmousedown時にはonClickは呼ばない
- if (tab.key) {
- emit('update:tab', tab.key);
- }
-}
-
-function onTabClick(t: Tab, ev: MouseEvent): void {
- if (t.key === props.tab) {
- top();
- } else if (t.onClick) {
- ev.preventDefault();
- ev.stopPropagation();
- t.onClick(ev);
- }
-
- if (t.key) {
- emit('update:tab', t.key);
- }
+function onTabClick(): void {
+ top();
}
const calcBg = () => {
@@ -156,88 +102,26 @@ const calcBg = () => {
bg.value = tinyBg.toRgbString();
};
-let ro1: ResizeObserver | null;
-let ro2: ResizeObserver | null;
-
-function renderTab() {
- const tabEl = props.tab ? tabRefs[props.tab] : undefined;
- if (tabEl && tabHighlightEl && tabHighlightEl.parentElement) {
- // offsetWidth や offsetLeft は少数を丸めてしまうため getBoundingClientRect を使う必要がある
- // https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetWidth#%E5%80%A4
- const parentRect = tabHighlightEl.parentElement.getBoundingClientRect();
- const rect = tabEl.getBoundingClientRect();
- tabHighlightEl.style.width = rect.width + 'px';
- tabHighlightEl.style.left = (rect.left - parentRect.left + tabHighlightEl.parentElement.scrollLeft) + 'px';
- }
-}
-
-function onTabWheel(ev: WheelEvent) {
- if (ev.deltaY !== 0 && ev.deltaX === 0) {
- ev.preventDefault();
- ev.stopPropagation();
- (ev.currentTarget as HTMLElement).scrollBy({
- left: ev.deltaY,
- behavior: 'smooth',
- });
- }
- return false;
-}
-
-function enter(el: HTMLElement) {
- const elementWidth = el.getBoundingClientRect().width;
- el.style.width = '0';
- el.offsetWidth; // reflow
- el.style.width = elementWidth + 'px';
- setTimeout(renderTab, 70);
-}
-function afterEnter(el: HTMLElement) {
- el.style.width = '';
- nextTick(renderTab);
-}
-function leave(el: HTMLElement) {
- const elementWidth = el.getBoundingClientRect().width;
- el.style.width = elementWidth + 'px';
- el.offsetWidth; // reflow
- el.style.width = '0';
-}
-function afterLeave(el: HTMLElement) {
- el.style.width = '';
-}
+let ro: ResizeObserver | null;
onMounted(() => {
calcBg();
globalEvents.on('themeChanged', calcBg);
- watch([() => props.tab, () => props.tabs], () => {
- nextTick(() => renderTab());
- }, {
- immediate: true,
- });
-
if (el && el.parentElement) {
narrow = el.parentElement.offsetWidth < 500;
- ro1 = new ResizeObserver((entries, observer) => {
+ ro = new ResizeObserver((entries, observer) => {
if (el && el.parentElement && document.body.contains(el as HTMLElement)) {
narrow = el.parentElement.offsetWidth < 500;
}
});
- ro1.observe(el.parentElement as HTMLElement);
- }
-
- if (el) {
- ro2 = new ResizeObserver((entries, observer) => {
- if (document.body.contains(el as HTMLElement)) {
- nextTick(() => renderTab());
- }
- });
- ro2.observe(el);
+ ro.observe(el.parentElement as HTMLElement);
}
});
onUnmounted(() => {
globalEvents.off('themeChanged', calcBg);
- if (ro1) ro1.disconnect();
- if (ro2) ro2.disconnect();
+ if (ro) ro.disconnect();
});
</script>
@@ -258,6 +142,7 @@ onUnmounted(() => {
.upper {
--height: 50px;
display: flex;
+ gap: var(--margin);
height: var(--height);
.tabs:first-child {
@@ -267,12 +152,9 @@ onUnmounted(() => {
padding-left: 16px;
mask-image: linear-gradient(90deg, rgba(0,0,0,0), rgb(0,0,0) 16px, rgb(0,0,0) 100%);
}
- .tabs:last-child {
+ .tabs {
margin-right: auto;
}
- .tabs:not(:last-child) {
- margin-right: 0;
- }
&.thin {
--height: 42px;
@@ -286,19 +168,14 @@ onUnmounted(() => {
&.slim {
text-align: center;
+ gap: 0;
+ .tabs:first-child {
+ margin-left: 0;
+ }
> .titleContainer {
- flex: 1;
margin: 0 auto;
max-width: 100%;
-
- > *:first-child {
- margin-left: auto;
- }
-
- > *:last-child {
- margin-right: auto;
- }
}
}
}
@@ -314,8 +191,6 @@ onUnmounted(() => {
align-items: center;
min-width: var(--height);
height: var(--height);
- margin: 0 var(--margin);
-
&:empty {
width: var(--height);
}
@@ -323,12 +198,12 @@ onUnmounted(() => {
.buttonsLeft {
composes: buttons;
- margin-right: auto;
+ margin: 0 var(--margin) 0 0;
}
.buttonsRight {
composes: buttons;
- margin-left: auto;
+ margin: 0 0 0 var(--margin);
}
.avatar {
@@ -373,7 +248,7 @@ onUnmounted(() => {
white-space: nowrap;
text-align: left;
font-weight: bold;
- flex-shrink: 0;
+ flex-shrink: 1;
margin-left: 24px;
}
@@ -418,68 +293,4 @@ onUnmounted(() => {
}
}
}
-
-.tabs {
- display: block;
- position: relative;
- margin: 0;
- height: var(--height);
- font-size: 0.8em;
- text-align: center;
- overflow-x: auto;
- overflow-y: hidden;
- scrollbar-width: none;
-
- &::-webkit-scrollbar {
- display: none;
- }
-}
-
-.tabsInner {
- display: inline-block;
- height: var(--height);
- white-space: nowrap;
-}
-
-.tab {
- display: inline-block;
- position: relative;
- padding: 0 10px;
- height: 100%;
- font-weight: normal;
- opacity: 0.7;
- transition: opacity 0.2s ease;
-
- &:hover {
- opacity: 1;
- }
-
- &.active {
- opacity: 1;
- }
-}
-
-.tabInner {
- display: flex;
- align-items: center;
-}
-
-.tabIcon + .tabTitle {
- margin-left: 8px;
-}
-
-.tabTitle {
- overflow: hidden;
- transition: width 0.15s ease-in-out;
-}
-
-.tabHighlight {
- position: absolute;
- bottom: 0;
- height: 3px;
- background: var(--accent);
- border-radius: 999px;
- transition: width 0.15s ease, left 0.15s ease;
- pointer-events: none;
-}
</style>