فهرست منبع

fix(ui): align sidebar pin controls

Keep the pin with the expanded header actions and center the collapsed version link with the navigation rail.
PathGao 23 ساعت پیش
والد
کامیت
b2fe233108
3فایلهای تغییر یافته به همراه36 افزوده شده و 31 حذف شده
  1. 17 17
      frontend/src/layouts/AppSidebar.css
  2. 10 11
      frontend/src/layouts/AppSidebar.tsx
  3. 9 3
      frontend/src/test/app-sidebar.test.tsx

+ 17 - 17
frontend/src/layouts/AppSidebar.css

@@ -34,9 +34,9 @@
   display: flex;
   align-items: center;
   justify-content: space-between;
-  gap: 8px;
+  gap: 4px;
   height: 58px;
-  padding: 0 16px 0 24px;
+  padding: 0 12px 0 16px;
   border-bottom: 1px solid var(--ant-color-border-secondary);
   user-select: none;
   white-space: nowrap;
@@ -53,7 +53,7 @@
 .brand-actions {
   display: inline-flex;
   align-items: center;
-  gap: 2px;
+  gap: 0;
   flex-shrink: 0;
 }
 
@@ -231,26 +231,26 @@
 }
 
 .sidebar-pin {
-  display: flex;
+  display: inline-flex;
   align-items: center;
-  gap: 10px;
-  width: 100%;
-  height: 34px;
-  padding: 0 16px;
+  justify-content: center;
+  width: 30px;
+  height: 30px;
+  padding: 0;
   border: none;
-  border-radius: 6px;
+  border-radius: 50%;
   background: transparent;
   color: var(--ant-color-text-secondary);
   cursor: pointer;
-  font-size: 13px;
-  text-align: left;
-  transition: background-color 0.2s, color 0.2s;
+  flex-shrink: 0;
+  transition: background-color 0.2s, transform 0.15s, color 0.2s;
 }
 
 .sidebar-pin:hover,
 .sidebar-pin:focus-visible {
   background-color: color-mix(in srgb, var(--ant-color-primary) 10%, transparent);
   color: var(--ant-color-primary);
+  transform: scale(1.08);
   outline: none;
 }
 
@@ -258,11 +258,6 @@
   font-size: 16px;
 }
 
-.ant-layout-sider-collapsed .sidebar-pin {
-  justify-content: center;
-  padding: 0;
-}
-
 .sider-version {
   display: flex;
   align-items: center;
@@ -278,6 +273,11 @@
   transition: color 0.2s;
 }
 
+.ant-layout-sider-collapsed .sider-version {
+  justify-content: center;
+  padding: 8px 0;
+}
+
 .sider-version .anticon {
   font-size: 16px;
 }

+ 10 - 11
frontend/src/layouts/AppSidebar.tsx

@@ -304,6 +304,16 @@ export default function AppSidebar() {
           </div>
           {!railCollapsed && (
             <div className="brand-actions">
+              <button
+                type="button"
+                className="sidebar-pin"
+                aria-label={t(pinned ? 'menu.unpinSidebar' : 'menu.pinSidebar')}
+                aria-pressed={pinned}
+                title={t(pinned ? 'menu.unpinSidebar' : 'menu.pinSidebar')}
+                onClick={togglePinned}
+              >
+                {pinned ? <PushpinFilled /> : <PushpinOutlined />}
+              </button>
               <DocsButton ariaLabel={t('menu.docs') || 'Documentation'} />
               <DonateButton ariaLabel={t('menu.donate') || 'Donate'} />
               <ThemeCycleButton
@@ -335,17 +345,6 @@ export default function AppSidebar() {
           onClick={onMenuClick}
         />
         <div className="sider-footer">
-          <button
-            type="button"
-            className="sidebar-pin"
-            aria-label={t(pinned ? 'menu.unpinSidebar' : 'menu.pinSidebar')}
-            aria-pressed={pinned}
-            title={t(pinned ? 'menu.unpinSidebar' : 'menu.pinSidebar')}
-            onClick={togglePinned}
-          >
-            {pinned ? <PushpinFilled /> : <PushpinOutlined />}
-            {!railCollapsed && <span>{t(pinned ? 'menu.unpinSidebar' : 'menu.pinSidebar')}</span>}
-          </button>
           <VersionBadge version={panelVersion} collapsed={railCollapsed} />
         </div>
       </Layout.Sider>

+ 9 - 3
frontend/src/test/app-sidebar.test.tsx

@@ -21,14 +21,20 @@ function renderSidebar() {
   );
 }
 
-test('keeps the sidebar expanded after pinning it and restores the choice', () => {
+test('keeps the sidebar expanded after pinning it from the header and restores the choice', () => {
   const first = renderSidebar();
   const sidebar = first.container.querySelector('.ant-layout-sider');
+  const sidebarRoot = first.container.querySelector('.ant-sidebar');
 
   expect(sidebar?.classList.contains('ant-layout-sider-collapsed')).toBe(true);
 
-  fireEvent.click(screen.getByRole('button', { name: 'Pin sidebar' }));
-  fireEvent.mouseLeave(first.container.querySelector('.ant-sidebar')!);
+  fireEvent.mouseEnter(sidebarRoot!);
+
+  const pinButton = screen.getByRole('button', { name: 'Pin sidebar' });
+  expect(pinButton.closest('.brand-actions')).not.toBeNull();
+
+  fireEvent.click(pinButton);
+  fireEvent.mouseLeave(sidebarRoot!);
 
   expect(sidebar?.classList.contains('ant-layout-sider-collapsed')).toBe(false);
   expect(localStorage.getItem('sidebar-pinned')).toBe('true');