feat: implement overlay scrollbar for app sidebar (#5820)
* feat: implement overlay scrollbar for app sidebar * pnpm prepr
This commit is contained in:
@@ -35,6 +35,7 @@
|
|||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
"intl-messageformat": "^10.7.7",
|
"intl-messageformat": "^10.7.7",
|
||||||
"ofetch": "^1.3.4",
|
"ofetch": "^1.3.4",
|
||||||
|
"overlayscrollbars": "^2.15.1",
|
||||||
"pinia": "^3.0.0",
|
"pinia": "^3.0.0",
|
||||||
"posthog-js": "^1.158.2",
|
"posthog-js": "^1.158.2",
|
||||||
"three": "^0.172.0",
|
"three": "^0.172.0",
|
||||||
|
|||||||
@@ -428,6 +428,13 @@ loading.startLoading()
|
|||||||
|
|
||||||
let suspensePending = false
|
let suspensePending = false
|
||||||
|
|
||||||
|
const sidebarOverlayScrollbarsOptions = Object.freeze({
|
||||||
|
overflow: {
|
||||||
|
x: 'hidden',
|
||||||
|
y: 'scroll',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
router.beforeEach(() => {
|
router.beforeEach(() => {
|
||||||
suspensePending = false
|
suspensePending = false
|
||||||
loading.startLoading()
|
loading.startLoading()
|
||||||
@@ -1279,29 +1286,26 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
|||||||
</RouterView>
|
</RouterView>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="app-sidebar mt-px shrink-0 flex flex-col border-0 border-l-[1px] border-[--brand-gradient-border] border-solid overflow-auto"
|
v-overlay-scrollbars="sidebarOverlayScrollbarsOptions"
|
||||||
|
class="app-sidebar mt-px shrink-0 flex flex-col border-0 border-l-[1px] border-[--brand-gradient-border] border-solid"
|
||||||
:class="{ 'has-plus': hasPlus }"
|
:class="{ 'has-plus': hasPlus }"
|
||||||
|
data-overlayscrollbars-initialize
|
||||||
>
|
>
|
||||||
<div
|
<div class="app-sidebar-scrollable flex-grow shrink relative" :class="{ 'pb-12': !hasPlus }">
|
||||||
class="app-sidebar-scrollable flex-grow shrink overflow-y-auto relative"
|
|
||||||
:class="{ 'pb-12': !hasPlus }"
|
|
||||||
>
|
|
||||||
<div id="sidebar-teleport-target" class="sidebar-teleport-content"></div>
|
<div id="sidebar-teleport-target" class="sidebar-teleport-content"></div>
|
||||||
<div class="sidebar-default-content" :class="{ 'sidebar-enabled': sidebarVisible }">
|
<div class="sidebar-default-content" :class="{ 'sidebar-enabled': sidebarVisible }">
|
||||||
<div
|
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
|
||||||
class="p-4 pr-1 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid"
|
|
||||||
>
|
|
||||||
<h3 class="text-base text-primary font-medium m-0">Playing as</h3>
|
<h3 class="text-base text-primary font-medium m-0">Playing as</h3>
|
||||||
<suspense>
|
<suspense>
|
||||||
<AccountsCard ref="accounts" mode="small" />
|
<AccountsCard ref="accounts" mode="small" />
|
||||||
</suspense>
|
</suspense>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
|
<div class="p-4 border-0 border-b-[1px] border-[--brand-gradient-border] border-solid">
|
||||||
<suspense>
|
<suspense>
|
||||||
<FriendsList :credentials="credentials" :sign-in="() => signIn()" />
|
<FriendsList :credentials="credentials" :sign-in="() => signIn()" />
|
||||||
</suspense>
|
</suspense>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="news && news.length > 0" class="p-4 pr-1 flex flex-col items-center">
|
<div v-if="news && news.length > 0" class="p-4 flex flex-col items-center">
|
||||||
<h3 class="text-base mb-4 text-primary font-medium m-0 text-left w-full">News</h3>
|
<h3 class="text-base mb-4 text-primary font-medium m-0 text-left w-full">News</h3>
|
||||||
<div class="space-y-4 flex flex-col items-center w-full">
|
<div class="space-y-4 flex flex-col items-center w-full">
|
||||||
<NewsArticleCard
|
<NewsArticleCard
|
||||||
@@ -1649,6 +1653,13 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style>
|
<style>
|
||||||
|
.os-theme-dark,
|
||||||
|
.os-theme-light {
|
||||||
|
--os-handle-bg: var(--color-scrollbar) !important;
|
||||||
|
--os-handle-bg-hover: var(--color-scrollbar) !important;
|
||||||
|
--os-handle-bg-active: var(--color-scrollbar) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.mac {
|
.mac {
|
||||||
.app-grid-statusbar {
|
.app-grid-statusbar {
|
||||||
padding-left: 5rem;
|
padding-left: 5rem;
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ const messages = defineMessages({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
<div v-if="userCredentials && !loading" class="flex gap-1 items-center mb-3 ml-2 mr-1">
|
<div v-if="userCredentials && !loading" class="flex gap-1 items-center mb-3 -ml-1">
|
||||||
<template v-if="sortedFriends.length > 0">
|
<template v-if="sortedFriends.length > 0">
|
||||||
<ButtonStyled circular type="transparent">
|
<ButtonStyled circular type="transparent">
|
||||||
<button
|
<button
|
||||||
@@ -309,7 +309,7 @@ const messages = defineMessages({
|
|||||||
@keyup.esc="search = ''"
|
@keyup.esc="search = ''"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<h3 v-else class="ml-2 w-full text-base text-primary font-medium m-0">
|
<h3 v-else class="w-full text-base text-primary font-medium m-0">
|
||||||
{{ formatMessage(messages.friends) }}
|
{{ formatMessage(messages.friends) }}
|
||||||
</h3>
|
</h3>
|
||||||
<ButtonStyled v-if="incomingRequests.length > 0" circular type="transparent">
|
<ButtonStyled v-if="incomingRequests.length > 0" circular type="transparent">
|
||||||
@@ -331,11 +331,11 @@ const messages = defineMessages({
|
|||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-3">
|
<div class="flex flex-col gap-3">
|
||||||
<h3 v-if="loading" class="ml-4 mr-1 text-base text-primary font-medium m-0">
|
<h3 v-if="loading" class="text-base text-primary font-medium m-0">
|
||||||
{{ formatMessage(messages.friends) }}
|
{{ formatMessage(messages.friends) }}
|
||||||
</h3>
|
</h3>
|
||||||
<template v-if="loading">
|
<template v-if="loading">
|
||||||
<div v-for="n in 5" :key="n" class="flex gap-2 items-center animate-pulse ml-4 mr-1">
|
<div v-for="n in 5" :key="n" class="flex gap-2 items-center animate-pulse">
|
||||||
<div class="min-w-9 min-h-9 bg-button-bg rounded-full"></div>
|
<div class="min-w-9 min-h-9 bg-button-bg rounded-full"></div>
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<div class="h-3 bg-button-bg rounded-full w-1/2 mb-1"></div>
|
<div class="h-3 bg-button-bg rounded-full w-1/2 mb-1"></div>
|
||||||
@@ -344,7 +344,7 @@ const messages = defineMessages({
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="sortedFriends.length === 0">
|
<template v-else-if="sortedFriends.length === 0">
|
||||||
<div class="text-sm ml-4 mr-1">
|
<div class="text-sm">
|
||||||
<div v-if="!userCredentials">
|
<div v-if="!userCredentials">
|
||||||
<IntlFormatted :message-id="messages.signInToAddFriends">
|
<IntlFormatted :message-id="messages.signInToAddFriends">
|
||||||
<template #link="{ children }">
|
<template #link="{ children }">
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ const messages = defineMessages({
|
|||||||
:open-by-default="openByDefault"
|
:open-by-default="openByDefault"
|
||||||
:force-open="isSearching"
|
:force-open="isSearching"
|
||||||
:button-class="
|
:button-class="
|
||||||
'pl-4 pr-3 flex w-full items-center bg-transparent border-0 p-0' +
|
'flex w-full items-center bg-transparent border-0 p-0' +
|
||||||
(isSearching
|
(isSearching
|
||||||
? ''
|
? ''
|
||||||
: ' cursor-pointer hover:brightness-[--hover-brightness] active:scale-[0.98] transition-all')
|
: ' cursor-pointer hover:brightness-[--hover-brightness] active:scale-[0.98] transition-all')
|
||||||
@@ -122,7 +122,7 @@ const messages = defineMessages({
|
|||||||
<div
|
<div
|
||||||
v-for="friend in friends"
|
v-for="friend in friends"
|
||||||
:key="friend.username"
|
:key="friend.username"
|
||||||
class="group grid items-center grid-cols-[auto_1fr_auto] gap-2 hover:bg-button-bg transition-colors rounded-full ml-4 mr-1"
|
class="group grid items-center grid-cols-[auto_1fr_auto] gap-2 hover:bg-button-bg transition-colors rounded-full mr-1"
|
||||||
@contextmenu.prevent.stop="
|
@contextmenu.prevent.stop="
|
||||||
(event) => friendOptions?.showMenu(event, friend, createContextMenuOptions(friend))
|
(event) => friendOptions?.showMenu(event, friend, createContextMenuOptions(friend))
|
||||||
"
|
"
|
||||||
|
|||||||
35
apps/app-frontend/src/directives/overlayScrollbars.ts
Normal file
35
apps/app-frontend/src/directives/overlayScrollbars.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { OverlayScrollbars, type PartialOptions } from 'overlayscrollbars'
|
||||||
|
import type { ObjectDirective } from 'vue'
|
||||||
|
|
||||||
|
const defaultOverlayScrollbarsOptions = Object.freeze<PartialOptions>({
|
||||||
|
scrollbars: {
|
||||||
|
theme: 'os-theme-dark',
|
||||||
|
autoHide: 'leave',
|
||||||
|
autoHideSuspend: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const mergeOptions = (options: PartialOptions = {}): PartialOptions => ({
|
||||||
|
...defaultOverlayScrollbarsOptions,
|
||||||
|
...options,
|
||||||
|
scrollbars: {
|
||||||
|
...defaultOverlayScrollbarsOptions.scrollbars,
|
||||||
|
...(options.scrollbars ?? {}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const overlayScrollbarsDirective: ObjectDirective<HTMLElement, PartialOptions | undefined> =
|
||||||
|
{
|
||||||
|
mounted(el, binding) {
|
||||||
|
OverlayScrollbars(el, mergeOptions(binding.value))
|
||||||
|
},
|
||||||
|
updated(el, binding) {
|
||||||
|
if (binding.value === binding.oldValue) return
|
||||||
|
const instance = OverlayScrollbars(el)
|
||||||
|
instance?.options(mergeOptions(binding.value))
|
||||||
|
},
|
||||||
|
unmounted(el) {
|
||||||
|
const instance = OverlayScrollbars(el)
|
||||||
|
instance?.destroy()
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'floating-vue/dist/style.css'
|
import 'floating-vue/dist/style.css'
|
||||||
|
import 'overlayscrollbars/overlayscrollbars.css'
|
||||||
|
|
||||||
import * as Sentry from '@sentry/vue'
|
import * as Sentry from '@sentry/vue'
|
||||||
import { VueScanPlugin } from '@taijased/vue-render-tracker'
|
import { VueScanPlugin } from '@taijased/vue-render-tracker'
|
||||||
@@ -8,6 +9,7 @@ import { createPinia } from 'pinia'
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
|
||||||
import App from '@/App.vue'
|
import App from '@/App.vue'
|
||||||
|
import { overlayScrollbarsDirective } from '@/directives/overlayScrollbars'
|
||||||
import i18nPlugin from '@/plugins/i18n'
|
import i18nPlugin from '@/plugins/i18n'
|
||||||
import i18nDebugPlugin from '@/plugins/i18n-debug'
|
import i18nDebugPlugin from '@/plugins/i18n-debug'
|
||||||
import router from '@/routes'
|
import router from '@/routes'
|
||||||
@@ -50,5 +52,6 @@ app.use(FloatingVue, {
|
|||||||
})
|
})
|
||||||
app.use(i18nPlugin)
|
app.use(i18nPlugin)
|
||||||
app.use(i18nDebugPlugin)
|
app.use(i18nDebugPlugin)
|
||||||
|
app.directive('overlay-scrollbars', overlayScrollbarsDirective)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
import type { FunctionalComponent, SVGAttributes } from 'vue'
|
import type { FunctionalComponent, SVGAttributes } from 'vue'
|
||||||
|
|
||||||
|
export type IconComponent = FunctionalComponent<SVGAttributes>
|
||||||
|
|
||||||
import _AffiliateIcon from './icons/affiliate.svg?component'
|
import _AffiliateIcon from './icons/affiliate.svg?component'
|
||||||
import _AlignLeftIcon from './icons/align-left.svg?component'
|
import _AlignLeftIcon from './icons/align-left.svg?component'
|
||||||
import _ArchiveIcon from './icons/archive.svg?component'
|
import _ArchiveIcon from './icons/archive.svg?component'
|
||||||
@@ -392,8 +394,6 @@ import _XCircleIcon from './icons/x-circle.svg?component'
|
|||||||
import _ZoomInIcon from './icons/zoom-in.svg?component'
|
import _ZoomInIcon from './icons/zoom-in.svg?component'
|
||||||
import _ZoomOutIcon from './icons/zoom-out.svg?component'
|
import _ZoomOutIcon from './icons/zoom-out.svg?component'
|
||||||
|
|
||||||
export type IconComponent = FunctionalComponent<SVGAttributes>
|
|
||||||
|
|
||||||
export const AffiliateIcon = _AffiliateIcon
|
export const AffiliateIcon = _AffiliateIcon
|
||||||
export const AlignLeftIcon = _AlignLeftIcon
|
export const AlignLeftIcon = _AlignLeftIcon
|
||||||
export const ArchiveIcon = _ArchiveIcon
|
export const ArchiveIcon = _ArchiveIcon
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -137,6 +137,9 @@ importers:
|
|||||||
ofetch:
|
ofetch:
|
||||||
specifier: ^1.3.4
|
specifier: ^1.3.4
|
||||||
version: 1.5.1
|
version: 1.5.1
|
||||||
|
overlayscrollbars:
|
||||||
|
specifier: ^2.15.1
|
||||||
|
version: 2.15.1
|
||||||
pinia:
|
pinia:
|
||||||
specifier: ^3.0.0
|
specifier: ^3.0.0
|
||||||
version: 3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))
|
version: 3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))
|
||||||
@@ -7604,6 +7607,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
overlayscrollbars@2.15.1:
|
||||||
|
resolution: {integrity: sha512-glX26JwjL+Tkzv0JNOWdW4VozP5dGXO+Wx8+TPrdTEJTSYT/8eJS8yXM+fewjU0nFq/JeCa+X+BqABNjC4YZSA==}
|
||||||
|
|
||||||
oxc-minify@0.110.0:
|
oxc-minify@0.110.0:
|
||||||
resolution: {integrity: sha512-KWGTzPo83QmGrXC4ml83PM9HDwUPtZFfasiclUvTV4i3/0j7xRRqINVkrL77CbQnoWura3CMxkRofjQKVDuhBw==}
|
resolution: {integrity: sha512-KWGTzPo83QmGrXC4ml83PM9HDwUPtZFfasiclUvTV4i3/0j7xRRqINVkrL77CbQnoWura3CMxkRofjQKVDuhBw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@@ -17717,6 +17723,8 @@ snapshots:
|
|||||||
type-check: 0.4.0
|
type-check: 0.4.0
|
||||||
word-wrap: 1.2.5
|
word-wrap: 1.2.5
|
||||||
|
|
||||||
|
overlayscrollbars@2.15.1: {}
|
||||||
|
|
||||||
oxc-minify@0.110.0:
|
oxc-minify@0.110.0:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@oxc-minify/binding-android-arm-eabi': 0.110.0
|
'@oxc-minify/binding-android-arm-eabi': 0.110.0
|
||||||
|
|||||||
Reference in New Issue
Block a user