fix(user-settings): collapse sidebar to icon-only rail on mobile (#15678)

## Summary

Improves the responsiveness of the User Settings layout by converting
the left navigation sidebar into a compact icon-only rail on mobile
devices.

Previously, the sidebar retained its full desktop width on narrow
viewports, reducing the available space for settings content and making
pages such as **Data Sources** difficult to use on phones and smaller
tablets.

With this change:

- Desktop layouts retain the existing full sidebar experience
- Mobile layouts (<768px) display a compact 64px icon-only navigation
rail
- Main content receives significantly more horizontal space
- Navigation and logout actions remain fully accessible on mobile

## Type of Change

- [x] Bug fix
## Screenshots

| Before | After |
|---------|---------|
| <img width="557" height="760" alt="image"
src="https://github.com/user-attachments/assets/fb0d6a90-2d57-464c-90c6-9097418c7c13"
/> | <img width="557" height="760" alt="image"
src="https://github.com/user-attachments/assets/8db36d0f-7070-41e1-b7b2-0fe9d0cceefb"
/> |

## What Changed

### Mobile Sidebar Optimization

- Added responsive mobile behavior using `useIsMobile()`
- Displays avatar and navigation icons only on mobile
- Hides user email, navigation labels, version information, theme
switcher, and logout text on smaller screens
- Preserves navigation and logout functionality through icon actions

### Layout Improvements

- Updated settings page grid layout to use fixed sidebar widths:
  - Mobile: `4rem` (64px)
  - Desktop: `303px`
- Uses `minmax(0, 1fr)` for the content panel to prevent overflow and
allow proper shrinking
- Prevents sidebar width from expanding based on content

## Impact

- Improves usability of User Settings pages on phones and small tablets
- Increases available space for settings content
- Reduces horizontal crowding and overflow issues
- Maintains the existing desktop experience

## Test Plan

### Desktop (≥768px)

- Verify the full sidebar is displayed
- Confirm email, navigation labels, version information, theme switch,
and logout text are visible
- Ensure all navigation items function correctly

### Mobile (<768px)

- Verify the sidebar collapses to a 64px icon-only rail
- Confirm main content remains readable without horizontal crowding
- Verify navigation icons route correctly:
  - Data Sources
  - Model Providers
  - MCP
  - Team
  - Profile
  - API
- Confirm logout works from the icon button

### Verification

- Run `npm run build`
- Hard refresh when testing production or Docker deployments
- Verify responsive behavior using browser device emulation
This commit is contained in:
Carl Harris
2026-06-11 04:28:44 -07:00
committed by GitHub
parent daa3811165
commit ec89fc036d
2 changed files with 31 additions and 31 deletions

View File

@@ -5,10 +5,14 @@ import { cn } from '@/lib/utils';
const UserSetting = () => {
return (
<section className="pt-8 size-full grid grid-cols-[auto_1fr] grid-rows-1">
<section className="pt-8 size-full grid grid-cols-[4rem_minmax(0,1fr)] md:grid-cols-[303px_minmax(0,1fr)] grid-rows-1 min-w-0">
<SideBar />
<div className={cn('pr-6 pb-6 flex flex-1 rounded-lg overflow-hidden')}>
<div
className={cn(
'pr-2 md:pr-6 pb-6 flex flex-1 min-w-0 rounded-lg overflow-hidden',
)}
>
<Outlet />
</div>
</section>

View File

@@ -13,6 +13,7 @@ import { Routes } from '@/routes';
import { TFunction } from 'i18next';
import {
LucideBox,
LucideLogOut,
LucideServer,
LucideUnplug,
LucideUser,
@@ -54,14 +55,6 @@ const menuItems = (t: TFunction) => [
label: t('setting.api'),
key: Routes.Api,
},
// {
// icon: MessageSquareQuote,
// label: 'Prompt Templates',
// key: Routes.Profile,
// },
// { icon: TextSearch, label: 'Retrieval Templates', key: Routes.Profile },
// { icon: Cog, label: t('setting.system'), key: Routes.System },
// { icon: Banknote, label: 'Plan', key: Routes.Plan },
];
export function SideBar() {
@@ -77,48 +70,43 @@ export function SideBar() {
const { logout } = useLogout();
return (
<aside className="w-[303px] bg-bg-base flex flex-col">
<aside className="shrink-0 w-16 md:w-[303px] bg-bg-base flex flex-col overflow-hidden">
<header>
<h1 className="px-6 flex gap-2.5 items-center font-normal">
<h1 className="px-2 md:px-6 flex gap-2.5 items-center justify-center md:justify-start font-normal">
<RAGFlowAvatar
avatar={userInfo?.avatar}
name={userInfo?.nickname}
isPerson
/>
<p className="text-sm text-text-primary">{userInfo?.email}</p>
<p className="hidden md:block text-sm text-text-primary truncate">
{userInfo?.email}
</p>
</h1>
</header>
<nav className="flex-1 overflow-auto mt-4 py-1">
<ul className="px-6 flex flex-col gap-5">
<ul className="px-2 md:px-6 flex flex-col gap-2 md:gap-5 items-center md:items-stretch">
{menuItems(t).map((item) => {
const { key, icon, label, ...rest } = item;
return (
<li key={key}>
<li key={key} className="w-full md:w-auto">
<Button
{...rest}
block
variant="ghost"
aria-label={label}
className={cn(
'justify-start gap-2.5 px-3 relative h-10 text-base',
'relative h-10 text-base max-md:size-10 max-md:p-0 max-md:justify-center justify-start gap-2.5 px-2 md:px-3',
activeItemKey === key && 'bg-bg-card text-text-primary',
)}
onClick={handleMenuClick(key)}
>
<section className="flex items-center gap-2.5">
<span className="flex items-center gap-2.5 max-md:gap-0">
{icon}
<span>{label}</span>
</section>
{/* {item.key === Routes.System && (
<div className="mr-2 px-2 bg-accent-primary-5 text-accent-primary rounded-md">
{version}
</div>
)} */}
{/* {active && (
<div className="absolute right-0 w-[5px] h-[66px] bg-primary rounded-l-xl shadow-[0_0_5.94px_#7561ff,0_0_11.88px_#7561ff,0_0_41.58px_#7561ff,0_0_83.16px_#7561ff,0_0_142.56px_#7561ff,0_0_249.48px_#7561ff]" />
)} */}
<span className="hidden md:inline">{label}</span>
</span>
</Button>
</li>
);
@@ -126,15 +114,23 @@ export function SideBar() {
</ul>
</nav>
<footer className="p-6 mt-auto">
<div className="flex items-center gap-2 mb-6 justify-between">
<footer className="p-2 md:p-6 mt-auto">
<div className="hidden md:flex items-center gap-2 mb-6 justify-between">
<span className="text-xs text-accent-primary">{version}</span>
<ThemeSwitch />
</div>
<Button block size="lg" variant="transparent" onClick={() => logout()}>
{t('setting.logout')}
<Button
block
size="lg"
variant="transparent"
aria-label={t('setting.logout')}
className="max-md:size-10 max-md:p-0 max-md:mx-auto max-md:justify-center"
onClick={() => logout()}
>
<LucideLogOut className="size-[1em] md:hidden" />
<span className="hidden md:inline">{t('setting.logout')}</span>
</Button>
</footer>
</aside>