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
07d2add6
Commit
07d2add6
authored
Apr 10, 2026
by
qingyuzhang
Browse files
fix(sidebar): smooth collapse transitions
parent
00c08c57
Changes
2
Show whitespace changes
Inline
Side-by-side
frontend/src/components/layout/AppSidebar.vue
View file @
07d2add6
...
@@ -7,20 +7,18 @@
...
@@ -7,20 +7,18 @@
]"
]"
>
>
<!-- Logo/Brand -->
<!-- Logo/Brand -->
<div
class=
"sidebar-header"
>
<div
class=
"sidebar-header"
:class=
"
{ 'sidebar-header-collapsed': sidebarCollapsed }"
>
<!-- Custom Logo or Default Logo -->
<!-- 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"
/>
<img
v-if=
"settingsLoaded"
:src=
"siteLogo || '/logo.png'"
alt=
"Logo"
class=
"h-full w-full object-contain"
/>
</div>
</div>
<transition
name=
"fade"
>
<div
class=
"sidebar-brand"
:class=
"
{ 'sidebar-brand-collapsed': sidebarCollapsed }" :aria-hidden="sidebarCollapsed ? 'true' : 'false'">
<div
v-if=
"!sidebarCollapsed"
class=
"flex flex-col"
>
<span
class=
"sidebar-brand-title text-lg font-bold text-gray-900 dark:text-white"
>
<span
class=
"text-lg font-bold text-gray-900 dark:text-white"
>
{{
siteName
}}
{{
siteName
}}
</span>
</span>
<!-- Version Badge -->
<!-- Version Badge -->
<VersionBadge
:version=
"siteVersion"
/>
<VersionBadge
:version=
"siteVersion"
/>
</div>
</div>
</transition>
</div>
</div>
<!-- Navigation -->
<!-- Navigation -->
...
@@ -34,7 +32,7 @@
...
@@ -34,7 +32,7 @@
:key=
"item.path"
:key=
"item.path"
:to=
"item.path"
:to=
"item.path"
class=
"sidebar-link mb-1"
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"
:title="sidebarCollapsed ? item.label : undefined"
:id="
:id="
item.path === '/admin/accounts'
item.path === '/admin/accounts'
...
@@ -49,34 +47,31 @@
...
@@ -49,34 +47,31 @@
>
>
<span
v-if=
"item.iconSvg"
class=
"h-5 w-5 flex-shrink-0 sidebar-svg-icon"
v-html=
"sanitizeSvg(item.iconSvg)"
></span>
<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"
/>
<component
v-else
:is=
"item.icon"
class=
"h-5 w-5 flex-shrink-0"
/>
<transition
name=
"fade"
>
<span
class=
"sidebar-label"
:class=
"
{ 'sidebar-label-collapsed': sidebarCollapsed }" :aria-hidden="sidebarCollapsed ? 'true' : 'false'">
{{
item
.
label
}}
</span>
<span
v-if=
"!sidebarCollapsed"
>
{{
item
.
label
}}
</span>
</transition>
</router-link>
</router-link>
</div>
</div>
<!-- Personal Section for Admin (hidden in simple mode) -->
<!-- Personal Section for Admin (hidden in simple mode) -->
<div
v-if=
"!authStore.isSimpleMode"
class=
"sidebar-section"
>
<div
v-if=
"!authStore.isSimpleMode"
class=
"sidebar-section"
>
<div
v-if=
"!sidebarCollapsed"
class=
"sidebar-section-title"
>
<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
'
)
}}
{{
t
(
'
nav.myAccount
'
)
}}
</span>
</div>
</div>
<div
v-else
class=
"mx-3 my-3 h-px bg-gray-200 dark:bg-dark-700"
></div>
<router-link
<router-link
v-for=
"item in personalNavItems"
v-for=
"item in personalNavItems"
:key=
"item.path"
:key=
"item.path"
:to=
"item.path"
:to=
"item.path"
class=
"sidebar-link mb-1"
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"
:title="sidebarCollapsed ? item.label : undefined"
:data-tour="item.path === '/keys' ? 'sidebar-my-keys' : undefined"
:data-tour="item.path === '/keys' ? 'sidebar-my-keys' : undefined"
@click="handleMenuItemClick(item.path)"
@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>
<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"
/>
<component
v-else
:is=
"item.icon"
class=
"h-5 w-5 flex-shrink-0"
/>
<transition
name=
"fade"
>
<span
class=
"sidebar-label"
:class=
"
{ 'sidebar-label-collapsed': sidebarCollapsed }" :aria-hidden="sidebarCollapsed ? 'true' : 'false'">
{{
item
.
label
}}
</span>
<span
v-if=
"!sidebarCollapsed"
>
{{
item
.
label
}}
</span>
</transition>
</router-link>
</router-link>
</div>
</div>
</
template
>
</
template
>
...
@@ -89,16 +84,14 @@
...
@@ -89,16 +84,14 @@
:key=
"item.path"
:key=
"item.path"
:to=
"item.path"
:to=
"item.path"
class=
"sidebar-link mb-1"
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"
:title="sidebarCollapsed ? item.label : undefined"
:data-tour="item.path === '/keys' ? 'sidebar-my-keys' : undefined"
:data-tour="item.path === '/keys' ? 'sidebar-my-keys' : undefined"
@click="handleMenuItemClick(item.path)"
@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>
<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"
/>
<component
v-else
:is=
"item.icon"
class=
"h-5 w-5 flex-shrink-0"
/>
<transition
name=
"fade"
>
<span
class=
"sidebar-label"
:class=
"
{ 'sidebar-label-collapsed': sidebarCollapsed }" :aria-hidden="sidebarCollapsed ? 'true' : 'false'">
{{
item
.
label
}}
</span>
<span
v-if=
"!sidebarCollapsed"
>
{{
item
.
label
}}
</span>
</transition>
</router-link>
</router-link>
</div>
</div>
</
template
>
</
template
>
...
@@ -110,28 +103,26 @@
...
@@ -110,28 +103,26 @@
<button
<button
@
click=
"toggleTheme"
@
click=
"toggleTheme"
class=
"sidebar-link mb-2 w-full"
class=
"sidebar-link mb-2 w-full"
:class=
"{ 'sidebar-link-collapsed': sidebarCollapsed }"
:title=
"sidebarCollapsed ? (isDark ? t('nav.lightMode') : t('nav.darkMode')) : undefined"
: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"
/>
<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"
/>
<MoonIcon
v-else
class=
"h-5 w-5 flex-shrink-0"
/>
<transition
name=
"fade"
>
<span
class=
"sidebar-label"
:class=
"{ 'sidebar-label-collapsed': sidebarCollapsed }"
:aria-hidden=
"sidebarCollapsed ? 'true' : 'false'"
>
{{
<span
v-if=
"!sidebarCollapsed"
>
{{
isDark ? t('nav.lightMode') : t('nav.darkMode')
isDark ? t('nav.lightMode') : t('nav.darkMode')
}}
</span>
}}
</span>
</transition>
</button>
</button>
<!-- Collapse Button -->
<!-- Collapse Button -->
<button
<button
@
click=
"toggleSidebar"
@
click=
"toggleSidebar"
class=
"sidebar-link w-full"
class=
"sidebar-link w-full"
:class=
"{ 'sidebar-link-collapsed': sidebarCollapsed }"
:title=
"sidebarCollapsed ? t('nav.expand') : t('nav.collapse')"
:title=
"sidebarCollapsed ? t('nav.expand') : t('nav.collapse')"
>
>
<ChevronDoubleLeftIcon
v-if=
"!sidebarCollapsed"
class=
"h-5 w-5 flex-shrink-0"
/>
<ChevronDoubleLeftIcon
v-if=
"!sidebarCollapsed"
class=
"h-5 w-5 flex-shrink-0"
/>
<ChevronDoubleRightIcon
v-else
class=
"h-5 w-5 flex-shrink-0"
/>
<ChevronDoubleRightIcon
v-else
class=
"h-5 w-5 flex-shrink-0"
/>
<transition
name=
"fade"
>
<span
class=
"sidebar-label"
:class=
"{ 'sidebar-label-collapsed': sidebarCollapsed }"
:aria-hidden=
"sidebarCollapsed ? 'true' : 'false'"
>
{{ t('nav.collapse') }}
</span>
<span
v-if=
"!sidebarCollapsed"
>
{{ t('nav.collapse') }}
</span>
</transition>
</button>
</button>
</div>
</div>
</aside>
</aside>
...
@@ -659,14 +650,113 @@ onMounted(() => {
...
@@ -659,14 +650,113 @@ onMounted(() => {
</
script
>
</
script
>
<
style
scoped
>
<
style
scoped
>
.fade-enter-active
,
.sidebar-logo
{
.fade-leave-active
{
flex
:
0
0
2.25rem
;
transition
:
opacity
0.2s
ease
;
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
;
}
.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
;
}
}
.
fade-enter-from
,
.
sidebar-label-collapsed
{
.fade-leave-to
{
max-width
:
0
;
opacity
:
0
;
opacity
:
0
;
transform
:
translateX
(
-4px
);
pointer-events
:
none
;
}
}
/* Custom SVG icon in sidebar: inherit color, constrain size */
/* Custom SVG icon in sidebar: inherit color, constrain size */
...
...
frontend/src/style.css
View file @
07d2add6
...
@@ -529,12 +529,17 @@
...
@@ -529,12 +529,17 @@
@apply
border-r
border-gray-200
dark
:
border-dark-800
;
@apply
border-r
border-gray-200
dark
:
border-dark-800
;
@apply
flex
flex-col;
@apply
flex
flex-col;
@apply
transition-transform
duration-300;
@apply
transition-transform
duration-300;
transition-property
:
width
,
transform
;
}
}
.sidebar-header
{
.sidebar-header
{
@apply
h-16
px-6;
@apply
h-16
px-6;
@apply
flex
items-center
gap-3;
@apply
flex
items-center
gap-3;
@apply
overflow-hidden;
@apply
border-b
border-gray-100
dark
:
border-dark-800
;
@apply
border-b
border-gray-100
dark
:
border-dark-800
;
transition
:
padding
0.2s
ease
,
gap
0.2s
ease
;
}
}
.sidebar-nav
{
.sidebar-nav
{
...
@@ -542,12 +547,15 @@
...
@@ -542,12 +547,15 @@
}
}
.sidebar-link
{
.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-sm
font-medium;
@apply
text-gray-600
dark
:
text-dark-300
;
@apply
text-gray-600
dark
:
text-dark-300
;
@apply
transition-all
duration-200;
@apply
transition-all
duration-200;
@apply
hover
:
bg-gray-100
dark
:
hover
:
bg-dark-800
;
@apply
hover
:
bg-gray-100
dark
:
hover
:
bg-dark-800
;
@apply
hover
:
text-gray-900
dark
:
hover
:
text-white
;
@apply
hover
:
text-gray-900
dark
:
hover
:
text-white
;
padding-left
:
1.0625rem
;
padding-right
:
0.875rem
;
}
}
.sidebar-link-active
{
.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