Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
16126a2c
Unverified
Commit
16126a2c
authored
Apr 12, 2026
by
Wesley Liddick
Committed by
GitHub
Apr 12, 2026
Browse files
Merge pull request #1545 from Zqysl/qingyu/fix-smooth-sidebar-collapse
fix(sidebar): smooth sidebar collapse behavior
parents
9b7b3755
c520de11
Changes
2
Hide whitespace changes
Inline
Side-by-side
frontend/src/components/layout/AppSidebar.vue
View file @
16126a2c
...
...
@@ -7,20 +7,18 @@
]"
>
<!-- Logo/Brand -->
<div
class=
"sidebar-header"
>
<div
class=
"sidebar-header"
:class=
"
{ 'sidebar-header-collapsed': sidebarCollapsed }"
>
<!-- Custom Logo or Default Logo -->
<div
class=
"flex h-9 w-9 items-center justify-center overflow-hidden rounded-xl shadow-glow"
>
<div
class=
"
sidebar-logo
flex h-9 w-9 items-center justify-center overflow-hidden rounded-xl shadow-glow"
>
<img
v-if=
"settingsLoaded"
:src=
"siteLogo || '/logo.png'"
alt=
"Logo"
class=
"h-full w-full object-contain"
/>
</div>
<transition
name=
"fade"
>
<div
v-if=
"!sidebarCollapsed"
class=
"flex flex-col"
>
<span
class=
"text-lg font-bold text-gray-900 dark:text-white"
>
{{
siteName
}}
</span>
<!-- Version Badge -->
<VersionBadge
:version=
"siteVersion"
/>
</div>
</transition>
<div
class=
"sidebar-brand"
:class=
"
{ 'sidebar-brand-collapsed': sidebarCollapsed }" :aria-hidden="sidebarCollapsed ? 'true' : 'false'">
<span
class=
"sidebar-brand-title text-lg font-bold text-gray-900 dark:text-white"
>
{{
siteName
}}
</span>
<!-- Version Badge -->
<VersionBadge
:version=
"siteVersion"
/>
</div>
</div>
<!-- Navigation -->
...
...
@@ -35,17 +33,25 @@
<button
type=
"button"
class=
"sidebar-link mb-1 w-full"
:class=
"
{ 'sidebar-link-active': isGroupActive(item)
&&
!isGroupExpanded(item) }"
:class=
"
{
'sidebar-link-active': isGroupActive(item)
&&
!isGroupExpanded(item),
'sidebar-link-collapsed': sidebarCollapsed
}"
:title="sidebarCollapsed ? item.label : undefined"
@click="sidebarCollapsed ? undefined : toggleGroup(item)"
>
<component
:is=
"item.icon"
class=
"h-5 w-5 flex-shrink-0"
/>
<transition
name=
"fade"
>
<span
v-if=
"!sidebarCollapsed"
class=
"flex flex-1 items-center justify-between"
>
<span>
{{
item
.
label
}}
</span>
<ChevronDownIcon
class=
"h-4 w-4 flex-shrink-0 transition-transform duration-200"
:class=
"isGroupExpanded(item) ? 'rotate-180' : ''"
/>
</span>
</transition>
<span
class=
"sidebar-label sidebar-label-flex"
:class=
"
{ 'sidebar-label-collapsed': sidebarCollapsed }"
:aria-hidden="sidebarCollapsed ? 'true' : 'false'"
>
<span
class=
"min-w-0 truncate"
>
{{
item
.
label
}}
</span>
<ChevronDownIcon
class=
"h-4 w-4 flex-shrink-0 transition-transform duration-200"
:class=
"isGroupExpanded(item) ? 'rotate-180' : ''"
/>
</span>
</button>
<!-- Children -->
<div
v-if=
"!sidebarCollapsed && isGroupExpanded(item)"
class=
"mb-1 ml-4 border-l border-gray-200 pl-2 dark:border-dark-600"
>
...
...
@@ -67,7 +73,7 @@
v-else
:to=
"item.path"
class=
"sidebar-link mb-1"
:class=
"{ 'sidebar-link-active': isActive(item.path) }"
:class=
"{ 'sidebar-link-active': isActive(item.path)
, 'sidebar-link-collapsed': sidebarCollapsed
}"
:title=
"sidebarCollapsed ? item.label : undefined"
:id=
"
item.path === '/admin/accounts'
...
...
@@ -82,35 +88,32 @@
>
<span
v-if=
"item.iconSvg"
class=
"h-5 w-5 flex-shrink-0 sidebar-svg-icon"
v-html=
"sanitizeSvg(item.iconSvg)"
></span>
<component
v-else
:is=
"item.icon"
class=
"h-5 w-5 flex-shrink-0"
/>
<transition
name=
"fade"
>
<span
v-if=
"!sidebarCollapsed"
>
{{ item.label }}
</span>
</transition>
<span
class=
"sidebar-label"
:class=
"{ 'sidebar-label-collapsed': sidebarCollapsed }"
:aria-hidden=
"sidebarCollapsed ? 'true' : 'false'"
>
{{ item.label }}
</span>
</router-link>
</template>
</div>
<!-- Personal Section for Admin (hidden in simple mode) -->
<div
v-if=
"!authStore.isSimpleMode"
class=
"sidebar-section"
>
<div
v-if=
"!sidebarCollapsed"
class=
"sidebar-section-title"
>
{{ t('nav.myAccount') }}
<div
class=
"sidebar-section-title"
:class=
"{ 'sidebar-section-title-collapsed': sidebarCollapsed }"
:aria-hidden=
"sidebarCollapsed ? 'true' : 'false'"
>
<span
class=
"sidebar-section-title-text"
:class=
"{ 'sidebar-section-title-text-collapsed': sidebarCollapsed }"
>
{{ t('nav.myAccount') }}
</span>
</div>
<div
v-else
class=
"mx-3 my-3 h-px bg-gray-200 dark:bg-dark-700"
></div>
<router-link
v-for=
"item in personalNavItems"
:key=
"item.path"
:to=
"item.path"
class=
"sidebar-link mb-1"
:class=
"{ 'sidebar-link-active': isActive(item.path) }"
:class=
"{ 'sidebar-link-active': isActive(item.path)
, 'sidebar-link-collapsed': sidebarCollapsed
}"
:title=
"sidebarCollapsed ? item.label : undefined"
:data-tour=
"item.path === '/keys' ? 'sidebar-my-keys' : undefined"
@
click=
"handleMenuItemClick(item.path)"
>
<span
v-if=
"item.iconSvg"
class=
"h-5 w-5 flex-shrink-0 sidebar-svg-icon"
v-html=
"sanitizeSvg(item.iconSvg)"
></span>
<component
v-else
:is=
"item.icon"
class=
"h-5 w-5 flex-shrink-0"
/>
<transition
name=
"fade"
>
<span
v-if=
"!sidebarCollapsed"
>
{{ item.label }}
</span>
</transition>
<span
class=
"sidebar-label"
:class=
"{ 'sidebar-label-collapsed': sidebarCollapsed }"
:aria-hidden=
"sidebarCollapsed ? 'true' : 'false'"
>
{{ item.label }}
</span>
</router-link>
</div>
</template>
...
...
@@ -123,16 +126,14 @@
:key=
"item.path"
:to=
"item.path"
class=
"sidebar-link mb-1"
:class=
"
{ 'sidebar-link-active': isActive(item.path) }"
:class=
"
{ 'sidebar-link-active': isActive(item.path)
, 'sidebar-link-collapsed': sidebarCollapsed
}"
:title="sidebarCollapsed ? item.label : undefined"
:data-tour="item.path === '/keys' ? 'sidebar-my-keys' : undefined"
@click="handleMenuItemClick(item.path)"
>
<span
v-if=
"item.iconSvg"
class=
"h-5 w-5 flex-shrink-0 sidebar-svg-icon"
v-html=
"sanitizeSvg(item.iconSvg)"
></span>
<component
v-else
:is=
"item.icon"
class=
"h-5 w-5 flex-shrink-0"
/>
<transition
name=
"fade"
>
<span
v-if=
"!sidebarCollapsed"
>
{{
item
.
label
}}
</span>
</transition>
<span
class=
"sidebar-label"
:class=
"
{ 'sidebar-label-collapsed': sidebarCollapsed }" :aria-hidden="sidebarCollapsed ? 'true' : 'false'">
{{
item
.
label
}}
</span>
</router-link>
</div>
</
template
>
...
...
@@ -144,28 +145,26 @@
<button
@
click=
"toggleTheme"
class=
"sidebar-link mb-2 w-full"
:class=
"{ 'sidebar-link-collapsed': sidebarCollapsed }"
:title=
"sidebarCollapsed ? (isDark ? t('nav.lightMode') : t('nav.darkMode')) : undefined"
>
<SunIcon
v-if=
"isDark"
class=
"h-5 w-5 flex-shrink-0 text-amber-500"
/>
<MoonIcon
v-else
class=
"h-5 w-5 flex-shrink-0"
/>
<transition
name=
"fade"
>
<span
v-if=
"!sidebarCollapsed"
>
{{
isDark ? t('nav.lightMode') : t('nav.darkMode')
}}
</span>
</transition>
<span
class=
"sidebar-label"
:class=
"{ 'sidebar-label-collapsed': sidebarCollapsed }"
:aria-hidden=
"sidebarCollapsed ? 'true' : 'false'"
>
{{
isDark ? t('nav.lightMode') : t('nav.darkMode')
}}
</span>
</button>
<!-- Collapse Button -->
<button
@
click=
"toggleSidebar"
class=
"sidebar-link w-full"
:class=
"{ 'sidebar-link-collapsed': sidebarCollapsed }"
:title=
"sidebarCollapsed ? t('nav.expand') : t('nav.collapse')"
>
<ChevronDoubleLeftIcon
v-if=
"!sidebarCollapsed"
class=
"h-5 w-5 flex-shrink-0"
/>
<ChevronDoubleRightIcon
v-else
class=
"h-5 w-5 flex-shrink-0"
/>
<transition
name=
"fade"
>
<span
v-if=
"!sidebarCollapsed"
>
{{ t('nav.collapse') }}
</span>
</transition>
<span
class=
"sidebar-label"
:class=
"{ 'sidebar-label-collapsed': sidebarCollapsed }"
:aria-hidden=
"sidebarCollapsed ? 'true' : 'false'"
>
{{ t('nav.collapse') }}
</span>
</button>
</div>
</aside>
...
...
@@ -794,14 +793,120 @@ onMounted(() => {
</
script
>
<
style
scoped
>
.fade-enter-active
,
.fade-leave-active
{
transition
:
opacity
0.2s
ease
;
.sidebar-logo
{
flex
:
0
0
2.25rem
;
min-width
:
2.25rem
;
}
.sidebar-header-collapsed
{
gap
:
0
;
padding-left
:
1.125rem
;
padding-right
:
1.125rem
;
}
.sidebar-brand
{
min-width
:
0
;
flex
:
1
1
auto
;
overflow
:
hidden
;
white-space
:
nowrap
;
transition
:
max-width
0.22s
ease
,
opacity
0.14s
ease
,
transform
0.14s
ease
;
max-width
:
12rem
;
}
.sidebar-brand-collapsed
{
max-width
:
0
;
opacity
:
0
;
transform
:
translateX
(
-4px
);
pointer-events
:
none
;
}
.sidebar-brand-title
{
display
:
block
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
.sidebar-link-collapsed
{
gap
:
0
;
padding-left
:
0.875rem
;
padding-right
:
0.875rem
;
}
.fade-enter-from
,
.fade-leave-to
{
.sidebar-section-title
{
position
:
relative
;
display
:
flex
;
align-items
:
center
;
min-height
:
1.25rem
;
overflow
:
hidden
;
white-space
:
nowrap
;
}
.sidebar-section-title-text
{
display
:
block
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
transition
:
opacity
0.16s
ease
,
transform
0.16s
ease
;
}
.sidebar-section-title
::after
{
content
:
''
;
position
:
absolute
;
left
:
0.75rem
;
right
:
0.75rem
;
top
:
50%
;
height
:
1px
;
background
:
rgb
(
229
231
235
);
opacity
:
0
;
transform
:
translateY
(
-50%
);
transition
:
opacity
0.18s
ease
;
}
.dark
.sidebar-section-title
::after
{
background
:
rgb
(
55
65
81
);
}
.sidebar-section-title-text-collapsed
{
opacity
:
0
;
transform
:
translateX
(
-4px
);
}
.sidebar-section-title-collapsed
::after
{
opacity
:
1
;
transition-delay
:
0.08s
;
}
.sidebar-label
{
display
:
block
;
min-width
:
0
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
transition
:
max-width
0.2s
ease
,
opacity
0.12s
ease
,
transform
0.12s
ease
;
max-width
:
12rem
;
}
.sidebar-label-flex
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
gap
:
0.5rem
;
}
.sidebar-label-collapsed
{
max-width
:
0
;
opacity
:
0
;
transform
:
translateX
(
-4px
);
pointer-events
:
none
;
}
/* Custom SVG icon in sidebar: constrain size without overriding uploaded SVG colors */
...
...
frontend/src/style.css
View file @
16126a2c
...
...
@@ -523,12 +523,17 @@
@apply
border-r
border-gray-200
dark
:
border-dark-800
;
@apply
flex
flex-col;
@apply
transition-transform
duration-300;
transition-property
:
width
,
transform
;
}
.sidebar-header
{
@apply
h-16
px-6;
@apply
flex
items-center
gap-3;
@apply
overflow-hidden;
@apply
border-b
border-gray-100
dark
:
border-dark-800
;
transition
:
padding
0.2s
ease
,
gap
0.2s
ease
;
}
.sidebar-nav
{
...
...
@@ -536,12 +541,15 @@
}
.sidebar-link
{
@apply
flex
items-center
gap-3
rounded-xl
px-3
py-2.5;
@apply
flex
items-center
gap-3
rounded-xl
py-2.5;
@apply
overflow-hidden;
@apply
text-sm
font-medium;
@apply
text-gray-600
dark
:
text-dark-300
;
@apply
transition-all
duration-200;
@apply
hover
:
bg-gray-100
dark
:
hover
:
bg-dark-800
;
@apply
hover
:
text-gray-900
dark
:
hover
:
text-white
;
padding-left
:
1.0625rem
;
padding-right
:
0.875rem
;
}
.sidebar-link-active
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment