Skip to content

Commit 60549a8

Browse files
committed
feat(react-ui): page-width archetype system + mobile/tablet nav polish
Replace the universal max-width:1200px cap on .page with a four-tier archetype system (narrow 760, medium 1080, default 1600, wide unbounded) selected per page based on what its UX actually wants. Data/table pages fill ultrawide displays; forms cap at reading width; tabbed feature surfaces breathe. Mobile/tablet: - New 640/1024 breakpoint split. Tablets (640-1023) get a persistent 52px icon rail; below 640 keeps the slide-off drawer. - Drawer polish: body-scroll lock, Escape to close, focus moves into the drawer on open and back to the hamburger on close, aria-hidden + inert on main while open. - Mobile top bar carries hamburger + theme toggle + account avatar (44x44 touch targets) so theme/account aren't trapped in the drawer. - Page-level reflow on phones: page-header column-stacks, filter chips scroll horizontally, tables go edge-to-edge, OperationsBar overflows rather than wrapping. Honors prefers-reduced-motion. Manage > Models: drop the toggle column; Enable/Disable joins the per-row Actions menu alongside Stop/Pin/Edit/Logs/Delete for consistency with the other action verbs. Page-width tokens live in theme.css so future tuning is one line. Removes 7 inline maxWidth workarounds from page roots. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude Code:claude-opus-4-7 [Edit] [Bash]
1 parent 54728e2 commit 60549a8

32 files changed

Lines changed: 398 additions & 117 deletions

core/http/react-ui/src/App.css

Lines changed: 233 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,55 @@
133133
.mobile-title {
134134
font-weight: 600;
135135
color: var(--color-text-primary);
136+
flex: 1;
137+
min-width: 0;
138+
overflow: hidden;
139+
text-overflow: ellipsis;
140+
white-space: nowrap;
141+
}
142+
143+
.mobile-header-actions {
144+
display: flex;
145+
align-items: center;
146+
gap: 4px;
147+
margin-left: auto;
148+
}
149+
150+
.mobile-header-btn {
151+
background: none;
152+
border: none;
153+
color: var(--color-text-primary);
154+
font-size: 1.05rem;
155+
cursor: pointer;
156+
display: inline-flex;
157+
align-items: center;
158+
justify-content: center;
159+
min-width: 44px;
160+
min-height: 44px;
161+
padding: 10px;
162+
border-radius: var(--radius-full);
163+
transition: background var(--duration-fast) var(--ease-default);
164+
}
165+
.mobile-header-btn:hover {
166+
background: var(--color-bg-hover);
167+
}
168+
.mobile-header-btn:focus-visible {
169+
outline: 2px solid var(--color-focus-ring);
170+
outline-offset: 2px;
171+
}
172+
173+
.mobile-header-avatar {
174+
padding: 4px;
175+
}
176+
.mobile-header-avatar img {
177+
width: 32px;
178+
height: 32px;
179+
border-radius: 50%;
180+
object-fit: cover;
181+
display: block;
182+
}
183+
.mobile-header-avatar .fa-user-circle {
184+
font-size: 1.5rem;
136185
}
137186

138187
/* Sidebar */
@@ -827,15 +876,22 @@
827876
color: var(--color-text-muted);
828877
}
829878

830-
/* Common page styles */
879+
/* Common page styles — width archetype is opt-in via modifiers.
880+
Default cap fits 9-column data tables on ultrawide displays without
881+
feeling untethered. Add .page--narrow for forms / single-record edit
882+
views, .page--wide for full-bleed (chat shells, log streams). */
831883
.page {
832884
padding: var(--spacing-xl) var(--spacing-xl) var(--spacing-2xl);
833-
max-width: 1200px;
834-
margin: 0 auto;
835885
width: 100%;
886+
max-width: var(--page-max, var(--page-max-default));
887+
margin: 0 auto;
836888
animation: fadeIn var(--duration-normal) var(--ease-default);
837889
}
838890

891+
.page--narrow { --page-max: var(--page-max-narrow); }
892+
.page--medium { --page-max: var(--page-max-medium); }
893+
.page--wide { --page-max: var(--page-max-wide); }
894+
839895
.page-header {
840896
margin-bottom: var(--spacing-xl);
841897
}
@@ -3855,7 +3911,7 @@ select.input {
38553911
grid-template-columns: minmax(320px, 420px) 1fr;
38563912
gap: var(--spacing-lg);
38573913
padding: var(--spacing-xl);
3858-
max-width: 1280px;
3914+
max-width: var(--page-max-default);
38593915
margin: 0 auto;
38603916
width: 100%;
38613917
align-items: start;
@@ -4070,66 +4126,158 @@ select.input {
40704126
color: var(--color-danger);
40714127
}
40724128

4073-
/* Responsive */
4129+
/* ============================================================
4130+
Responsive
4131+
------------------------------------------------------------
4132+
Three viewport tiers, expressed as cascading media queries:
4133+
4134+
* desktop (≥1024px) — full sidebar, content margin-left = sidebar-width
4135+
* tablet (640–1023) — 52px icon rail; tap hamburger to overlay-expand
4136+
* mobile (<640px) — sidebar slides off-screen behind a top-bar drawer
4137+
4138+
Touch-target minimums apply across both tablet and mobile via the
4139+
first (max-width: 1023px) block.
4140+
============================================================ */
4141+
4142+
/* Touch-friendly sizing + shared layout simplifications (tablet + mobile) */
40744143
@media (max-width: 1023px) {
4075-
.main-content,
4076-
.sidebar-is-collapsed .main-content {
4077-
margin-left: 0;
4144+
.hamburger-btn {
4145+
min-width: 44px;
4146+
min-height: 44px;
4147+
padding: 10px 12px;
4148+
display: inline-flex;
4149+
align-items: center;
4150+
justify-content: center;
40784151
}
40794152

4080-
.mobile-header {
4081-
display: flex;
4153+
.nav-item {
4154+
min-height: 44px;
40824155
}
40834156

4084-
.sidebar {
4085-
transform: translateX(-100%);
4086-
width: var(--sidebar-width);
4157+
.sidebar-close-btn {
4158+
min-width: 44px;
4159+
min-height: 44px;
4160+
align-items: center;
4161+
justify-content: center;
4162+
padding: 10px;
40874163
}
40884164

4089-
.sidebar.collapsed {
4090-
width: var(--sidebar-width);
4165+
.mobile-header {
4166+
min-height: 56px;
40914167
}
40924168

4093-
.sidebar.open {
4094-
transform: translateX(0);
4169+
/* Layouts that need to collapse to single-column on any narrow viewport */
4170+
.chat-sidebar { display: none; }
4171+
.chat-settings-drawer {
4172+
width: 100%;
4173+
max-width: 100%;
40954174
}
4175+
.media-layout { grid-template-columns: 1fr; }
4176+
.media-controls { position: static; }
4177+
.page { padding: var(--spacing-md); }
40964178

4097-
.sidebar-close-btn {
4098-
display: block;
4179+
/* The desktop collapse chevron is desktop-only — tablets auto-rail,
4180+
mobile uses the hamburger. */
4181+
.sidebar-collapse-btn { display: none; }
4182+
}
4183+
4184+
/* Tablet (640–1023): persistent icon rail; tap hamburger to overlay-expand */
4185+
@media (max-width: 1023px) and (min-width: 640px) {
4186+
.main-content,
4187+
.sidebar-is-collapsed .main-content {
4188+
margin-left: var(--sidebar-width-collapsed);
40994189
}
41004190

4101-
.sidebar-collapse-btn {
4102-
display: none;
4191+
.mobile-header { display: none; }
4192+
4193+
.sidebar {
4194+
width: var(--sidebar-width-collapsed);
4195+
transform: translateX(0);
4196+
}
4197+
.sidebar.collapsed { width: var(--sidebar-width-collapsed); }
4198+
4199+
/* Apply collapsed visuals while not pinned-open. These mirror the
4200+
existing .sidebar.collapsed desktop rules so we re-use one look. */
4201+
.sidebar:not(.open) .nav-label,
4202+
.sidebar:not(.open) .nav-external,
4203+
.sidebar:not(.open) .sidebar-section-title,
4204+
.sidebar:not(.open) .sidebar-section-chevron { display: none; }
4205+
.sidebar:not(.open) .sidebar-logo-link { display: none; }
4206+
.sidebar:not(.open) .sidebar-logo-icon {
4207+
display: flex;
4208+
align-items: center;
4209+
justify-content: center;
4210+
width: 100%;
4211+
}
4212+
.sidebar:not(.open) .sidebar-header { justify-content: center; }
4213+
.sidebar:not(.open) .nav-item {
4214+
justify-content: center;
4215+
padding: 8px 0;
4216+
border-left-width: 2px;
4217+
}
4218+
.sidebar:not(.open) .nav-icon { width: auto; font-size: 1rem; }
4219+
.sidebar:not(.open) .sidebar-footer {
4220+
justify-content: center;
4221+
flex-direction: column;
4222+
gap: var(--spacing-xs);
41034223
}
4224+
.sidebar:not(.open) .sidebar-user-name,
4225+
.sidebar:not(.open) .sidebar-logout-btn { display: none; }
41044226

4105-
.sidebar.collapsed .nav-label,
4106-
.sidebar.collapsed .nav-external,
4107-
.sidebar.collapsed .sidebar-section-title {
4108-
display: unset;
4227+
/* Pinned open: overlay the full sidebar on top of content */
4228+
.sidebar.open {
4229+
width: var(--sidebar-width);
4230+
box-shadow: var(--shadow-md);
41094231
}
41104232

4111-
.sidebar.collapsed .sidebar-logo-link {
4233+
.sidebar-close-btn { display: none; }
4234+
.sidebar.open .sidebar-close-btn { display: flex; }
4235+
4236+
.sidebar-overlay {
41124237
display: block;
4238+
position: fixed;
4239+
inset: 0;
4240+
background: rgba(0, 0, 0, 0.5);
4241+
z-index: 40;
41134242
}
4243+
}
41144244

4115-
.sidebar.collapsed .sidebar-logo-icon {
4116-
display: none;
4245+
/* Mobile (<640): sidebar slides off-screen as a drawer */
4246+
@media (max-width: 639px) {
4247+
.main-content,
4248+
.sidebar-is-collapsed .main-content {
4249+
margin-left: 0;
41174250
}
41184251

4252+
.mobile-header { display: flex; }
4253+
4254+
.sidebar {
4255+
transform: translateX(-100%);
4256+
width: var(--sidebar-width);
4257+
}
4258+
.sidebar.collapsed { width: var(--sidebar-width); }
4259+
.sidebar.open { transform: translateX(0); }
4260+
4261+
.sidebar-close-btn { display: flex; }
4262+
4263+
/* When opened on mobile, even if the .collapsed class is present
4264+
from desktop preference, force the expanded look — drawer always
4265+
shows full labels. */
4266+
.sidebar.collapsed .nav-label,
4267+
.sidebar.collapsed .nav-external,
4268+
.sidebar.collapsed .sidebar-section-title { display: unset; }
4269+
.sidebar.collapsed .sidebar-logo-link { display: block; }
4270+
.sidebar.collapsed .sidebar-logo-icon { display: none; }
41194271
.sidebar.collapsed .nav-item {
41204272
justify-content: flex-start;
4121-
padding: 6px var(--spacing-sm);
4273+
padding: 10px var(--spacing-md);
41224274
border-left-width: 3px;
41234275
}
4124-
41254276
.sidebar.collapsed .nav-icon {
41264277
width: 18px;
41274278
font-size: 0.85rem;
41284279
}
4129-
4130-
.sidebar.collapsed .sidebar-header {
4131-
justify-content: space-between;
4132-
}
4280+
.sidebar.collapsed .sidebar-header { justify-content: space-between; }
41334281

41344282
.sidebar-overlay {
41354283
display: block;
@@ -4138,26 +4286,64 @@ select.input {
41384286
background: rgba(0, 0, 0, 0.5);
41394287
z-index: 40;
41404288
}
4289+
}
41414290

4142-
.chat-sidebar {
4143-
display: none;
4291+
/* Mobile reflow polish — phone-only (<640) layout adjustments for
4292+
page chrome that was designed flex-row on desktop. */
4293+
@media (max-width: 639px) {
4294+
/* Page header: stack title block + inline-action cluster vertically */
4295+
.page-header {
4296+
flex-direction: column;
4297+
align-items: stretch;
4298+
gap: var(--spacing-md);
41444299
}
41454300

4146-
.chat-settings-drawer {
4147-
width: 100%;
4148-
max-width: 100%;
4301+
/* Filter chip rows scroll horizontally instead of wrapping into walls */
4302+
.filter-bar {
4303+
flex-wrap: nowrap;
4304+
overflow-x: auto;
4305+
-webkit-overflow-scrolling: touch;
4306+
scrollbar-width: none;
41494307
}
4150-
4151-
.media-layout {
4152-
grid-template-columns: 1fr;
4308+
.filter-bar::-webkit-scrollbar { display: none; }
4309+
.filter-btn { flex-shrink: 0; }
4310+
4311+
.search-bar { min-width: 0; }
4312+
4313+
/* Tables go edge-to-edge; offsetting against .page padding gives full
4314+
bleed without changing the table layout itself. */
4315+
.table-container {
4316+
border-radius: 0;
4317+
border-left: 0;
4318+
border-right: 0;
4319+
margin-inline: calc(-1 * var(--spacing-md));
41534320
}
41544321

4155-
.media-controls {
4156-
position: static;
4322+
/* Operations toasts: scroll horizontally instead of wrapping */
4323+
.operations-bar {
4324+
overflow-x: auto;
4325+
flex-wrap: nowrap;
4326+
-webkit-overflow-scrolling: touch;
41574327
}
4328+
.operation-item { flex-shrink: 0; }
4329+
.operation-text {
4330+
max-width: 60vw;
4331+
overflow: hidden;
4332+
text-overflow: ellipsis;
4333+
white-space: nowrap;
4334+
}
4335+
}
41584336

4159-
.page {
4160-
padding: var(--spacing-md);
4337+
/* Reduced motion — disable non-essential transitions for users who
4338+
request it. Keeps focus/state changes accessible without animation. */
4339+
@media (prefers-reduced-motion: reduce) {
4340+
.sidebar,
4341+
.page-transition,
4342+
.operations-bar,
4343+
.page,
4344+
.main-content {
4345+
transition: none !important;
4346+
animation: none !important;
41614347
}
41624348
}
41634349

@@ -4939,7 +5125,7 @@ select.input {
49395125

49405126
.biometrics-page {
49415127
padding: var(--spacing-xl);
4942-
max-width: 1320px;
5128+
max-width: var(--page-max-wide);
49435129
margin: 0 auto;
49445130
width: 100%;
49455131
animation: fadeIn var(--duration-normal) var(--ease-default);

0 commit comments

Comments
 (0)