diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-02-12 11:20:17 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2023-02-12 11:20:17 +0900 |
| commit | c5ef6bf38ada420e80091891f57beeaa6b87b68c (patch) | |
| tree | d2f2f7b3c4586df1ab1af0cf61af2ca5571810cc /packages/frontend/src/components | |
| parent | Merge branch 'develop' (diff) | |
| parent | 13.6.1 (diff) | |
| download | misskey-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.vue | 42 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkNoteDetailed.vue | 32 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkNoteSimple.vue | 12 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkPagination.vue | 2 | ||||
| -rw-r--r-- | packages/frontend/src/components/global/MkPageHeader.tabs.vue | 234 | ||||
| -rw-r--r-- | packages/frontend/src/components/global/MkPageHeader.vue | 233 |
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> |