Browse Source

Update responsive breakpoints for various components, implement useIsSidebarCompact hook

Matteo Parenti 3 months ago
parent
commit
4ff3e2a60f

+ 10 - 10
frontend/src/components/AMSHistoryModal.tsx

@@ -187,7 +187,7 @@ export function AMSHistoryModal({
         {/* Content */}
         {/* Content */}
         <div className="p-6 space-y-6 overflow-y-auto max-h-[calc(90vh-80px)]">
         <div className="p-6 space-y-6 overflow-y-auto max-h-[calc(90vh-80px)]">
           {/* Time Range & Mode Selector */}
           {/* Time Range & Mode Selector */}
-          <div className="flex items-center justify-between max-[511px]:flex-col max-[511px]:items-start max-[511px]:gap-3">
+          <div className="flex items-center justify-between max-[550px]:flex-col max-[550px]:items-start max-[550px]:gap-3">
             <div className="inline-flex gap-1 rounded-lg p-1 max-w-full flex-wrap w-fit" style={{ backgroundColor: cardBg }}>
             <div className="inline-flex gap-1 rounded-lg p-1 max-w-full flex-wrap w-fit" style={{ backgroundColor: cardBg }}>
               <button
               <button
                 onClick={() => setMode('humidity')}
                 onClick={() => setMode('humidity')}
@@ -228,10 +228,10 @@ export function AMSHistoryModal({
           </div>
           </div>
 
 
           {/* Stats Cards */}
           {/* Stats Cards */}
-          <div className="grid grid-cols-4 gap-4 max-[511px]:grid-cols-2">
+          <div className="grid grid-cols-4 gap-4 max-[550px]:grid-cols-2">
             {mode === 'humidity' ? (
             {mode === 'humidity' ? (
               <>
               <>
-                <div className="rounded-lg p-4 max-[511px]:order-2" style={{ backgroundColor: cardBg }}>
+                <div className="rounded-lg p-4 max-[550px]:order-2" style={{ backgroundColor: cardBg }}>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.current', 'Current')}</p>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.current', 'Current')}</p>
                   <div className="flex items-center gap-2">
                   <div className="flex items-center gap-2">
                     <p className="text-2xl font-bold" style={{ color: getHumidityColor(currentHumidity) }}>
                     <p className="text-2xl font-bold" style={{ color: getHumidityColor(currentHumidity) }}>
@@ -240,19 +240,19 @@ export function AMSHistoryModal({
                     <TrendIcon trend={humidityTrend} />
                     <TrendIcon trend={humidityTrend} />
                   </div>
                   </div>
                 </div>
                 </div>
-                <div className="rounded-lg p-4 max-[511px]:order-4" style={{ backgroundColor: cardBg }}>
+                <div className="rounded-lg p-4 max-[550px]:order-4" style={{ backgroundColor: cardBg }}>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.average', 'Average')}</p>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.average', 'Average')}</p>
                   <p className="text-2xl font-bold" style={{ color: textPrimary }}>
                   <p className="text-2xl font-bold" style={{ color: textPrimary }}>
                     {data?.avg_humidity != null ? `${data.avg_humidity}%` : '—'}
                     {data?.avg_humidity != null ? `${data.avg_humidity}%` : '—'}
                   </p>
                   </p>
                 </div>
                 </div>
-                <div className="rounded-lg p-4 max-[511px]:order-1" style={{ backgroundColor: cardBg }}>
+                <div className="rounded-lg p-4 max-[550px]:order-1" style={{ backgroundColor: cardBg }}>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.min', 'Min')}</p>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.min', 'Min')}</p>
                   <p className="text-2xl font-bold text-green-500">
                   <p className="text-2xl font-bold text-green-500">
                     {data?.min_humidity != null ? `${data.min_humidity}%` : '—'}
                     {data?.min_humidity != null ? `${data.min_humidity}%` : '—'}
                   </p>
                   </p>
                 </div>
                 </div>
-                <div className="rounded-lg p-4 max-[511px]:order-3" style={{ backgroundColor: cardBg }}>
+                <div className="rounded-lg p-4 max-[550px]:order-3" style={{ backgroundColor: cardBg }}>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.max', 'Max')}</p>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.max', 'Max')}</p>
                   <p className="text-2xl font-bold text-red-500">
                   <p className="text-2xl font-bold text-red-500">
                     {data?.max_humidity != null ? `${data.max_humidity}%` : '—'}
                     {data?.max_humidity != null ? `${data.max_humidity}%` : '—'}
@@ -261,7 +261,7 @@ export function AMSHistoryModal({
               </>
               </>
             ) : (
             ) : (
               <>
               <>
-                <div className="rounded-lg p-4 max-[511px]:order-2" style={{ backgroundColor: cardBg }}>
+                <div className="rounded-lg p-4 max-[550px]:order-2" style={{ backgroundColor: cardBg }}>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.current', 'Current')}</p>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.current', 'Current')}</p>
                   <div className="flex items-center gap-2">
                   <div className="flex items-center gap-2">
                     <p className="text-2xl font-bold" style={{ color: getTempColor(currentTemp) }}>
                     <p className="text-2xl font-bold" style={{ color: getTempColor(currentTemp) }}>
@@ -270,19 +270,19 @@ export function AMSHistoryModal({
                     <TrendIcon trend={tempTrend} />
                     <TrendIcon trend={tempTrend} />
                   </div>
                   </div>
                 </div>
                 </div>
-                <div className="rounded-lg p-4 max-[511px]:order-4" style={{ backgroundColor: cardBg }}>
+                <div className="rounded-lg p-4 max-[550px]:order-4" style={{ backgroundColor: cardBg }}>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.average', 'Average')}</p>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.average', 'Average')}</p>
                   <p className="text-2xl font-bold" style={{ color: textPrimary }}>
                   <p className="text-2xl font-bold" style={{ color: textPrimary }}>
                     {data?.avg_temperature != null ? `${data.avg_temperature}°C` : '—'}
                     {data?.avg_temperature != null ? `${data.avg_temperature}°C` : '—'}
                   </p>
                   </p>
                 </div>
                 </div>
-                <div className="rounded-lg p-4 max-[511px]:order-1" style={{ backgroundColor: cardBg }}>
+                <div className="rounded-lg p-4 max-[550px]:order-1" style={{ backgroundColor: cardBg }}>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.min', 'Min')}</p>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.min', 'Min')}</p>
                   <p className="text-2xl font-bold text-blue-500">
                   <p className="text-2xl font-bold text-blue-500">
                     {data?.min_temperature != null ? `${data.min_temperature}°C` : '—'}
                     {data?.min_temperature != null ? `${data.min_temperature}°C` : '—'}
                   </p>
                   </p>
                 </div>
                 </div>
-                <div className="rounded-lg p-4 max-[511px]:order-3" style={{ backgroundColor: cardBg }}>
+                <div className="rounded-lg p-4 max-[550px]:order-3" style={{ backgroundColor: cardBg }}>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.max', 'Max')}</p>
                   <p className="text-xs" style={{ color: textSecondary }}>{t('common.max', 'Max')}</p>
                   <p className="text-2xl font-bold text-red-500">
                   <p className="text-2xl font-bold text-red-500">
                     {data?.max_temperature != null ? `${data.max_temperature}°C` : '—'}
                     {data?.max_temperature != null ? `${data.max_temperature}°C` : '—'}

+ 18 - 20
frontend/src/components/Dashboard.tsx

@@ -178,7 +178,7 @@ export function Dashboard({ widgets, storageKey, columns = 4, stackBelow, hideCo
     if (!stackBelow) return undefined;
     if (!stackBelow) return undefined;
     const mediaQuery = window.matchMedia(`(max-width: ${stackBelow}px)`);
     const mediaQuery = window.matchMedia(`(max-width: ${stackBelow}px)`);
     const handleChange = (event: MediaQueryListEvent | MediaQueryList) => {
     const handleChange = (event: MediaQueryListEvent | MediaQueryList) => {
-      setIsStacked('matches' in event ? event.matches : event.matches);
+      setIsStacked(event.matches);
     };
     };
     handleChange(mediaQuery);
     handleChange(mediaQuery);
     const onChange = (event: MediaQueryListEvent) => handleChange(event);
     const onChange = (event: MediaQueryListEvent) => handleChange(event);
@@ -355,25 +355,23 @@ export function Dashboard({ widgets, storageKey, columns = 4, stackBelow, hideCo
               gridTemplateColumns: `repeat(${effectiveColumns}, minmax(0, 1fr))`,
               gridTemplateColumns: `repeat(${effectiveColumns}, minmax(0, 1fr))`,
             }}
             }}
           >
           >
-            {visibleWidgets.map((widget) => (
-              (() => {
-                const size = layout.sizes[widget.id] || 2;
-                const columnSpan = Math.min(size, effectiveColumns);
-                return (
-              <SortableWidget
-                key={widget.id}
-                id={widget.id}
-                title={widget.title}
-                component={widget.component}
-                isHidden={layout.hidden.includes(widget.id)}
-                size={size}
-                columnSpan={columnSpan}
-                onToggleVisibility={() => toggleVisibility(widget.id)}
-                onToggleSize={() => toggleSize(widget.id)}
-              />
-                );
-              })()
-            ))}
+            {visibleWidgets.map((widget) => {
+              const size = layout.sizes[widget.id] || 2;
+              const columnSpan = Math.min(size, effectiveColumns);
+              return (
+                <SortableWidget
+                  key={widget.id}
+                  id={widget.id}
+                  title={widget.title}
+                  component={widget.component}
+                  isHidden={layout.hidden.includes(widget.id)}
+                  size={size}
+                  columnSpan={columnSpan}
+                  onToggleVisibility={() => toggleVisibility(widget.id)}
+                  onToggleSize={() => toggleSize(widget.id)}
+                />
+              );
+            })}
           </div>
           </div>
         </SortableContext>
         </SortableContext>
       </DndContext>
       </DndContext>

+ 2 - 2
frontend/src/components/FilamentTrends.tsx

@@ -157,7 +157,7 @@ export function FilamentTrends({ archives, currency = '$' }: FilamentTrendsProps
   return (
   return (
     <div className="space-y-6">
     <div className="space-y-6">
       {/* Time Range Selector */}
       {/* Time Range Selector */}
-      <div className="flex items-center justify-between max-[546px]:flex-col max-[546px]:items-start max-[546px]:gap-2">
+      <div className="flex items-center justify-between max-[550px]:flex-col max-[550px]:items-start max-[550px]:gap-2">
         <h3 className="text-lg font-semibold text-white">Filament Usage Trends</h3>
         <h3 className="text-lg font-semibold text-white">Filament Usage Trends</h3>
         <div className="flex gap-1 bg-bambu-dark rounded-lg p-1">
         <div className="flex gap-1 bg-bambu-dark rounded-lg p-1">
           {(['7d', '30d', '90d', '365d', 'all'] as TimeRange[]).map((range) => (
           {(['7d', '30d', '90d', '365d', 'all'] as TimeRange[]).map((range) => (
@@ -177,7 +177,7 @@ export function FilamentTrends({ archives, currency = '$' }: FilamentTrendsProps
       </div>
       </div>
 
 
       {/* Summary Cards */}
       {/* Summary Cards */}
-      <div className="grid grid-cols-3 gap-4 max-[600px]:grid-cols-1">
+      <div className="grid grid-cols-3 gap-4 max-[640px]:grid-cols-1">
         <div className="bg-bambu-dark rounded-lg p-4">
         <div className="bg-bambu-dark rounded-lg p-4">
           <div className="flex items-center justify-between gap-3">
           <div className="flex items-center justify-between gap-3">
             <p className="text-sm text-bambu-gray leading-none">Period Filament</p>
             <p className="text-sm text-bambu-gray leading-none">Period Filament</p>

+ 28 - 28
frontend/src/components/Layout.tsx

@@ -8,7 +8,7 @@ import { SwitchbarPopover } from './SwitchbarPopover';
 import { useQuery } from '@tanstack/react-query';
 import { useQuery } from '@tanstack/react-query';
 import { api, supportApi, pendingUploadsApi } from '../api/client';
 import { api, supportApi, pendingUploadsApi } from '../api/client';
 import { getIconByName } from './IconPicker';
 import { getIconByName } from './IconPicker';
-import { useIsMobile } from '../hooks/useIsMobile';
+import { useIsSidebarCompact } from '../hooks/useIsSidebarCompact';
 import { useAuth } from '../contexts/AuthContext';
 import { useAuth } from '../contexts/AuthContext';
 import { useToast } from '../contexts/ToastContext';
 import { useToast } from '../contexts/ToastContext';
 import { Card, CardHeader, CardContent } from './Card';
 import { Card, CardHeader, CardContent } from './Card';
@@ -72,7 +72,7 @@ export function Layout() {
   const location = useLocation();
   const location = useLocation();
   const { mode, toggleMode } = useTheme();
   const { mode, toggleMode } = useTheme();
   const { t } = useTranslation();
   const { t } = useTranslation();
-  const isMobile = useIsMobile();
+  const isSidebarCompact = useIsSidebarCompact();
   const { user, authEnabled, logout, hasPermission } = useAuth();
   const { user, authEnabled, logout, hasPermission } = useAuth();
   const { showToast } = useToast();
   const { showToast } = useToast();
   const [showChangePasswordModal, setShowChangePasswordModal] = useState(false);
   const [showChangePasswordModal, setShowChangePasswordModal] = useState(false);
@@ -311,12 +311,12 @@ export function Layout() {
     localStorage.setItem('sidebarExpanded', String(sidebarExpanded));
     localStorage.setItem('sidebarExpanded', String(sidebarExpanded));
   }, [sidebarExpanded]);
   }, [sidebarExpanded]);
 
 
-  // Close mobile drawer on navigation
+  // Close compact drawer on navigation
   useEffect(() => {
   useEffect(() => {
-    if (isMobile) {
+    if (isSidebarCompact) {
       setMobileDrawerOpen(false);
       setMobileDrawerOpen(false);
     }
     }
-  }, [location.pathname, isMobile]);
+  }, [location.pathname, isSidebarCompact]);
 
 
   // Listen for plate detection warnings (objects on plate, print paused)
   // Listen for plate detection warnings (objects on plate, print paused)
   // Only show to users with printers:control permission
   // Only show to users with printers:control permission
@@ -390,8 +390,8 @@ export function Layout() {
 
 
   return (
   return (
     <div className="flex min-h-screen">
     <div className="flex min-h-screen">
-      {/* Mobile Header */}
-      {isMobile && (
+      {/* Compact Header */}
+      {isSidebarCompact && (
         <header className="fixed top-0 left-0 right-0 z-40 h-14 bg-bambu-dark-secondary border-b border-bambu-dark-tertiary flex items-center px-4">
         <header className="fixed top-0 left-0 right-0 z-40 h-14 bg-bambu-dark-secondary border-b border-bambu-dark-tertiary flex items-center px-4">
           <button
           <button
             onClick={() => setMobileDrawerOpen(true)}
             onClick={() => setMobileDrawerOpen(true)}
@@ -408,8 +408,8 @@ export function Layout() {
         </header>
         </header>
       )}
       )}
 
 
-      {/* Mobile Drawer Backdrop */}
-      {isMobile && mobileDrawerOpen && (
+      {/* Compact Drawer Backdrop */}
+      {isSidebarCompact && mobileDrawerOpen && (
         <div
         <div
           className="fixed inset-0 bg-black/60 z-40 transition-opacity"
           className="fixed inset-0 bg-black/60 z-40 transition-opacity"
           onClick={() => setMobileDrawerOpen(false)}
           onClick={() => setMobileDrawerOpen(false)}
@@ -419,17 +419,17 @@ export function Layout() {
       {/* Sidebar / Mobile Drawer */}
       {/* Sidebar / Mobile Drawer */}
       <aside
       <aside
         className={`bg-bambu-dark-secondary border-r border-bambu-dark-tertiary flex flex-col transition-all duration-300 ${
         className={`bg-bambu-dark-secondary border-r border-bambu-dark-tertiary flex flex-col transition-all duration-300 ${
-          isMobile
+          isSidebarCompact
             ? `fixed inset-y-0 left-0 z-50 w-72 transform ${mobileDrawerOpen ? 'translate-x-0' : '-translate-x-full'}`
             ? `fixed inset-y-0 left-0 z-50 w-72 transform ${mobileDrawerOpen ? 'translate-x-0' : '-translate-x-full'}`
             : `fixed inset-y-0 left-0 z-30 ${sidebarExpanded ? 'w-64' : 'w-16'}`
             : `fixed inset-y-0 left-0 z-30 ${sidebarExpanded ? 'w-64' : 'w-16'}`
         }`}
         }`}
       >
       >
         {/* Logo */}
         {/* Logo */}
-        <div className={`border-b border-bambu-dark-tertiary flex items-center justify-center ${isMobile || sidebarExpanded ? 'p-4' : 'p-2'}`}>
+        <div className={`border-b border-bambu-dark-tertiary flex items-center justify-center ${isSidebarCompact || sidebarExpanded ? 'p-4' : 'p-2'}`}>
           <img
           <img
             src={mode === 'dark' ? '/img/bambuddy_logo_dark_transparent.png' : '/img/bambuddy_logo_light.png'}
             src={mode === 'dark' ? '/img/bambuddy_logo_dark_transparent.png' : '/img/bambuddy_logo_light.png'}
             alt="Bambuddy"
             alt="Bambuddy"
-            className={isMobile || sidebarExpanded ? 'h-16 w-auto' : 'h-8 w-8 object-cover object-left'}
+            className={isSidebarCompact || sidebarExpanded ? 'h-16 w-auto' : 'h-8 w-8 object-cover object-left'}
           />
           />
         </div>
         </div>
 
 
@@ -467,10 +467,10 @@ export function Layout() {
                         href={link.url}
                         href={link.url}
                         target="_blank"
                         target="_blank"
                         rel="noopener noreferrer"
                         rel="noopener noreferrer"
-                        className={`flex items-center ${isMobile || sidebarExpanded ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition-colors group text-bambu-gray-light hover:bg-bambu-dark-tertiary hover:text-white`}
-                        title={!isMobile && !sidebarExpanded ? link.name : undefined}
+                        className={`flex items-center ${isSidebarCompact || sidebarExpanded ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition-colors group text-bambu-gray-light hover:bg-bambu-dark-tertiary hover:text-white`}
+                        title={!isSidebarCompact && !sidebarExpanded ? link.name : undefined}
                       >
                       >
-                        {sidebarExpanded && !isMobile && (
+                        {sidebarExpanded && !isSidebarCompact && (
                           <GripVertical className="w-4 h-4 flex-shrink-0 opacity-0 group-hover:opacity-50 cursor-grab active:cursor-grabbing -ml-1" />
                           <GripVertical className="w-4 h-4 flex-shrink-0 opacity-0 group-hover:opacity-50 cursor-grab active:cursor-grabbing -ml-1" />
                         )}
                         )}
                         {link.custom_icon ? (
                         {link.custom_icon ? (
@@ -482,21 +482,21 @@ export function Layout() {
                         ) : (
                         ) : (
                           LinkIcon && <LinkIcon className="w-5 h-5 flex-shrink-0" />
                           LinkIcon && <LinkIcon className="w-5 h-5 flex-shrink-0" />
                         )}
                         )}
-                        {(isMobile || sidebarExpanded) && <span>{link.name}</span>}
+                        {(isSidebarCompact || sidebarExpanded) && <span>{link.name}</span>}
                       </a>
                       </a>
                     ) : (
                     ) : (
                       <NavLink
                       <NavLink
                         to={`/external/${link.id}`}
                         to={`/external/${link.id}`}
                         className={({ isActive }) =>
                         className={({ isActive }) =>
-                          `flex items-center ${isMobile || sidebarExpanded ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition-colors group ${
+                          `flex items-center ${isSidebarCompact || sidebarExpanded ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition-colors group ${
                             isActive
                             isActive
                               ? 'bg-bambu-green text-white'
                               ? 'bg-bambu-green text-white'
                               : 'text-bambu-gray-light hover:bg-bambu-dark-tertiary hover:text-white'
                               : 'text-bambu-gray-light hover:bg-bambu-dark-tertiary hover:text-white'
                           }`
                           }`
                         }
                         }
-                        title={!isMobile && !sidebarExpanded ? link.name : undefined}
+                        title={!isSidebarCompact && !sidebarExpanded ? link.name : undefined}
                       >
                       >
-                        {sidebarExpanded && !isMobile && (
+                        {sidebarExpanded && !isSidebarCompact && (
                           <GripVertical className="w-4 h-4 flex-shrink-0 opacity-0 group-hover:opacity-50 cursor-grab active:cursor-grabbing -ml-1" />
                           <GripVertical className="w-4 h-4 flex-shrink-0 opacity-0 group-hover:opacity-50 cursor-grab active:cursor-grabbing -ml-1" />
                         )}
                         )}
                         {link.custom_icon ? (
                         {link.custom_icon ? (
@@ -508,7 +508,7 @@ export function Layout() {
                         ) : (
                         ) : (
                           LinkIcon && <LinkIcon className="w-5 h-5 flex-shrink-0" />
                           LinkIcon && <LinkIcon className="w-5 h-5 flex-shrink-0" />
                         )}
                         )}
-                        {(isMobile || sidebarExpanded) && <span>{link.name}</span>}
+                        {(isSidebarCompact || sidebarExpanded) && <span>{link.name}</span>}
                       </NavLink>
                       </NavLink>
                     )}
                     )}
                   </li>
                   </li>
@@ -544,15 +544,15 @@ export function Layout() {
                     <NavLink
                     <NavLink
                       to={to}
                       to={to}
                       className={({ isActive }) =>
                       className={({ isActive }) =>
-                        `flex items-center ${isMobile || sidebarExpanded ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition-colors group ${
+                        `flex items-center ${isSidebarCompact || sidebarExpanded ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition-colors group ${
                           isActive
                           isActive
                             ? 'bg-bambu-green text-white'
                             ? 'bg-bambu-green text-white'
                             : 'text-bambu-gray-light hover:bg-bambu-dark-tertiary hover:text-white'
                             : 'text-bambu-gray-light hover:bg-bambu-dark-tertiary hover:text-white'
                         }`
                         }`
                       }
                       }
-                      title={!isMobile && !sidebarExpanded ? t(labelKey) : undefined}
+                      title={!isSidebarCompact && !sidebarExpanded ? t(labelKey) : undefined}
                     >
                     >
-                      {sidebarExpanded && !isMobile && (
+                      {sidebarExpanded && !isSidebarCompact && (
                         <GripVertical className="w-4 h-4 flex-shrink-0 opacity-0 group-hover:opacity-50 cursor-grab active:cursor-grabbing -ml-1" />
                         <GripVertical className="w-4 h-4 flex-shrink-0 opacity-0 group-hover:opacity-50 cursor-grab active:cursor-grabbing -ml-1" />
                       )}
                       )}
                       <div className="relative">
                       <div className="relative">
@@ -565,7 +565,7 @@ export function Layout() {
                           </span>
                           </span>
                         )}
                         )}
                       </div>
                       </div>
-                      {(isMobile || sidebarExpanded) && <span>{t(labelKey)}</span>}
+                      {(isSidebarCompact || sidebarExpanded) && <span>{t(labelKey)}</span>}
                     </NavLink>
                     </NavLink>
                   </li>
                   </li>
                 );
                 );
@@ -574,8 +574,8 @@ export function Layout() {
           </ul>
           </ul>
         </nav>
         </nav>
 
 
-        {/* Collapse toggle - hide on mobile */}
-        {!isMobile && (
+        {/* Collapse toggle - hide on compact sidebar */}
+        {!isSidebarCompact && (
           <button
           <button
             onClick={() => setSidebarExpanded(!sidebarExpanded)}
             onClick={() => setSidebarExpanded(!sidebarExpanded)}
             className="p-2 mx-2 mb-2 rounded-lg hover:bg-bambu-dark-tertiary transition-colors text-bambu-gray-light hover:text-white flex items-center justify-center"
             className="p-2 mx-2 mb-2 rounded-lg hover:bg-bambu-dark-tertiary transition-colors text-bambu-gray-light hover:text-white flex items-center justify-center"
@@ -591,7 +591,7 @@ export function Layout() {
 
 
         {/* Footer */}
         {/* Footer */}
         <div className="p-2 border-t border-bambu-dark-tertiary">
         <div className="p-2 border-t border-bambu-dark-tertiary">
-          {isMobile || sidebarExpanded ? (
+          {isSidebarCompact || sidebarExpanded ? (
             <div className="flex flex-col gap-2 px-2">
             <div className="flex flex-col gap-2 px-2">
               {/* Top row: icons */}
               {/* Top row: icons */}
               <div className="flex items-center justify-center gap-1">
               <div className="flex items-center justify-center gap-1">
@@ -783,7 +783,7 @@ export function Layout() {
 
 
       {/* Main content */}
       {/* Main content */}
       <main className={`flex-1 bg-bambu-dark overflow-auto transition-all duration-300 ${
       <main className={`flex-1 bg-bambu-dark overflow-auto transition-all duration-300 ${
-        isMobile ? 'mt-14' : sidebarExpanded ? 'ml-64' : 'ml-16'
+        isSidebarCompact ? 'mt-14' : sidebarExpanded ? 'ml-64' : 'ml-16'
       }`}>
       }`}>
         {/* Debug logging indicator */}
         {/* Debug logging indicator */}
         {debugLoggingState?.enabled && (
         {debugLoggingState?.enabled && (

+ 1 - 1
frontend/src/hooks/useIsMobile.ts

@@ -1,6 +1,6 @@
 import { useState, useEffect } from 'react';
 import { useState, useEffect } from 'react';
 
 
-const MOBILE_BREAKPOINT = 1144; // hide sidebar below this width
+const MOBILE_BREAKPOINT = 768;
 
 
 export function useIsMobile(): boolean {
 export function useIsMobile(): boolean {
   const [isMobile, setIsMobile] = useState(() =>
   const [isMobile, setIsMobile] = useState(() =>

+ 24 - 0
frontend/src/hooks/useIsSidebarCompact.ts

@@ -0,0 +1,24 @@
+import { useState, useEffect } from 'react';
+
+const SIDEBAR_COMPACT_BREAKPOINT = 1144;
+
+export function useIsSidebarCompact(): boolean {
+  const [isCompact, setIsCompact] = useState(() =>
+    typeof window !== 'undefined' ? window.innerWidth < SIDEBAR_COMPACT_BREAKPOINT : false
+  );
+
+  useEffect(() => {
+    const mediaQuery = window.matchMedia(`(max-width: ${SIDEBAR_COMPACT_BREAKPOINT - 1}px)`);
+
+    const handleChange = (e: MediaQueryListEvent) => {
+      setIsCompact(e.matches);
+    };
+
+    setIsCompact(mediaQuery.matches);
+
+    mediaQuery.addEventListener('change', handleChange);
+    return () => mediaQuery.removeEventListener('change', handleChange);
+  }, []);
+
+  return isCompact;
+}

+ 1 - 1
frontend/src/pages/MaintenancePage.tsx

@@ -351,7 +351,7 @@ function MaintenanceCard({
         </div>
         </div>
 
 
         {/* Actions */}
         {/* Actions */}
-        <div className="flex items-center gap-2 shrink-0 max-[550px]:w-full max-[550px]:justify-end max-[550px]:mt+1">
+        <div className="flex items-center gap-2 shrink-0 max-[550px]:w-full max-[550px]:justify-end max-[550px]:mt-1">
           <span title={!hasPermission('maintenance:update') ? t('maintenance.noPermissionUpdate') : undefined}>
           <span title={!hasPermission('maintenance:update') ? t('maintenance.noPermissionUpdate') : undefined}>
             <Toggle
             <Toggle
               checked={item.enabled}
               checked={item.enabled}

+ 7 - 7
frontend/src/pages/PrintersPage.tsx

@@ -2626,9 +2626,9 @@ function PrinterCard({
                     <div className="flex-1 h-px bg-bambu-dark-tertiary/30" />
                     <div className="flex-1 h-px bg-bambu-dark-tertiary/30" />
                   </div>
                   </div>
 
 
-                  <div className="flex items-center justify-between gap-2 max-[480px]:items-start">
+                  <div className="flex items-center justify-between gap-2 max-[550px]:items-start">
                     {/* Left: Fan Status - always visible, dynamic coloring */}
                     {/* Left: Fan Status - always visible, dynamic coloring */}
-                    <div className="flex items-center gap-2 min-w-0 max-[480px]:flex-wrap max-[480px]:items-start max-[480px]:gap-1.5">
+                    <div className="flex items-center gap-2 min-w-0 max-[550px]:flex-wrap max-[550px]:items-start max-[550px]:gap-1.5">
                       {/* Part Cooling Fan */}
                       {/* Part Cooling Fan */}
                       <div
                       <div
                         className={`flex items-center gap-1 px-1.5 py-1 rounded ${partFan && partFan > 0 ? 'bg-cyan-500/10' : 'bg-bambu-dark'}`}
                         className={`flex items-center gap-1 px-1.5 py-1 rounded ${partFan && partFan > 0 ? 'bg-cyan-500/10' : 'bg-bambu-dark'}`}
@@ -2664,7 +2664,7 @@ function PrinterCard({
                     </div>
                     </div>
 
 
                     {/* Right: Print Control Buttons */}
                     {/* Right: Print Control Buttons */}
-                    <div className="flex items-center gap-2 flex-shrink-0 max-[480px]:self-start">
+                    <div className="flex items-center gap-2 flex-shrink-0 max-[550px]:self-start">
                       {/* Stop button */}
                       {/* Stop button */}
                       <button
                       <button
                         onClick={() => setShowStopConfirm(true)}
                         onClick={() => setShowStopConfirm(true)}
@@ -2750,7 +2750,7 @@ function PrinterCard({
                                 )}
                                 )}
                               </div>
                               </div>
                               {(ams.humidity != null || ams.temp != null) && (
                               {(ams.humidity != null || ams.temp != null) && (
-                                <div className="flex items-center gap-1.5 max-[480px]:flex-col max-[480px]:items-start">
+                                <div className="flex items-center gap-1.5 max-[550px]:flex-col max-[550px]:items-start">
                                   {ams.humidity != null && (
                                   {ams.humidity != null && (
                                     <HumidityIndicator
                                     <HumidityIndicator
                                       humidity={ams.humidity}
                                       humidity={ams.humidity}
@@ -3104,9 +3104,9 @@ function PrinterCard({
                               )}
                               )}
                             </div>
                             </div>
                             {/* Row 2: Slot (left) + Stats (right stacked) */}
                             {/* Row 2: Slot (left) + Stats (right stacked) */}
-                            <div className="flex gap-1.5 max-[480px]:flex-col max-[480px]:items-start">
+                            <div className="flex gap-1.5 max-[550px]:flex-col max-[550px]:items-start">
                               {/* Slot wrapper with menu button, dropdown, and loading overlay */}
                               {/* Slot wrapper with menu button, dropdown, and loading overlay */}
-                              <div className="relative group flex-1 max-[480px]:w-full">
+                              <div className="relative group flex-1 max-[550px]:w-full">
                                 {/* Loading overlay during RFID re-read */}
                                 {/* Loading overlay during RFID re-read */}
                                 {isHtRefreshing && (
                                 {isHtRefreshing && (
                                   <div className="absolute inset-0 bg-bambu-dark-tertiary/80 rounded flex items-center justify-center z-20">
                                   <div className="absolute inset-0 bg-bambu-dark-tertiary/80 rounded flex items-center justify-center z-20">
@@ -3230,7 +3230,7 @@ function PrinterCard({
                               </div>
                               </div>
                               {/* Stats stacked vertically: Temp on top, Humidity below */}
                               {/* Stats stacked vertically: Temp on top, Humidity below */}
                               {(ams.humidity != null || ams.temp != null) && (
                               {(ams.humidity != null || ams.temp != null) && (
-                                <div className="flex flex-col justify-center gap-1 shrink-0 max-[480px]:w-full">
+                                <div className="flex flex-col justify-center gap-1 shrink-0 max-[550px]:w-full">
                                   {ams.temp != null && (
                                   {ams.temp != null && (
                                     <TemperatureIndicator
                                     <TemperatureIndicator
                                       temp={ams.temp}
                                       temp={ams.temp}

+ 6 - 6
frontend/src/pages/ProfilesPage.tsx

@@ -262,7 +262,7 @@ function LoginForm({ onSuccess, t }: { onSuccess: () => void; t: TFunction }) {
             </div>
             </div>
           )}
           )}
 
 
-          <div className="flex gap-2 max-[510px]:flex-wrap max-[510px]:items-center">
+          <div className="flex gap-2 max-[550px]:flex-wrap max-[550px]:items-center">
             {step === 'code' && (
             {step === 'code' && (
               <Button type="button" variant="secondary" onClick={() => setStep('email')} className="flex-1">
               <Button type="button" variant="secondary" onClick={() => setStep('email')} className="flex-1">
                 {t('profiles.login.back')}
                 {t('profiles.login.back')}
@@ -1815,7 +1815,7 @@ function CreatePresetModal({
           </div>
           </div>
 
 
           {/* Tabs */}
           {/* Tabs */}
-          <div className="flex border-b border-bambu-dark-tertiary max-[565px]:flex-wrap max-[565px]:items-center">
+          <div className="flex border-b border-bambu-dark-tertiary max-[640px]:flex-wrap max-[640px]:items-center">
             <button
             <button
               onClick={() => setActiveTab('common')}
               onClick={() => setActiveTab('common')}
               className={`flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors border-b-2 -mb-px ${
               className={`flex items-center gap-2 px-4 py-3 text-sm font-medium transition-colors border-b-2 -mb-px ${
@@ -1844,7 +1844,7 @@ function CreatePresetModal({
               JSON
               JSON
               {jsonError && <AlertCircle className="w-3 h-3 text-red-400" />}
               {jsonError && <AlertCircle className="w-3 h-3 text-red-400" />}
             </button>
             </button>
-            <div className="flex-1 max-[565px]:hidden" />
+            <div className="flex-1 max-[640px]:hidden" />
             <button
             <button
               onClick={() => {
               onClick={() => {
                 const exportData = {
                 const exportData = {
@@ -2010,9 +2010,9 @@ function CreatePresetModal({
                   <h3 className="text-sm font-medium text-white mb-3">{t('profiles.presets.commonSettings')}</h3>
                   <h3 className="text-sm font-medium text-white mb-3">{t('profiles.presets.commonSettings')}</h3>
                   <div className="grid grid-cols-2 gap-x-6 gap-y-3">
                   <div className="grid grid-cols-2 gap-x-6 gap-y-3">
                     {dynamicFields.slice(0, 10).map(field => (
                     {dynamicFields.slice(0, 10).map(field => (
-                      <div key={field.key} className="flex items-center justify-between gap-4 max-[633px]:flex-col max-[633px]:items-start">
+                      <div key={field.key} className="flex items-center justify-between gap-4 max-[640px]:flex-col max-[640px]:items-start">
                         <label className="text-sm text-bambu-gray flex-shrink-0">{field.label}</label>
                         <label className="text-sm text-bambu-gray flex-shrink-0">{field.label}</label>
-                        <div className="w-48 max-[633px]:w-full">{renderFieldInput(field)}</div>
+                        <div className="w-48 max-[640px]:w-full">{renderFieldInput(field)}</div>
                       </div>
                       </div>
                     ))}
                     ))}
                   </div>
                   </div>
@@ -2460,7 +2460,7 @@ function CloudProfilesView({
             />
             />
           </div>
           </div>
 
 
-          <div className="flex gap-2 max-[510px]:flex-wrap max-[510px]:items-center">
+          <div className="flex gap-2 max-[550px]:flex-wrap max-[550px]:items-center">
             <Button
             <Button
               variant={compareMode ? 'primary' : 'secondary'}
               variant={compareMode ? 'primary' : 'secondary'}
               onClick={() => {
               onClick={() => {

+ 1 - 1
frontend/src/pages/StatsPage.tsx

@@ -760,7 +760,7 @@ export function StatsPage() {
         key={dashboardKey}
         key={dashboardKey}
         widgets={widgets}
         widgets={widgets}
         storageKey="bambusy-dashboard-layout"
         storageKey="bambusy-dashboard-layout"
-        stackBelow={766}
+        stackBelow={640}
         hideControls
         hideControls
       />
       />
     </div>
     </div>