* Add comprehensive mobile support with responsive navigation

- Add hamburger drawer navigation for mobile (< 768px)
  - Create useIsMobile hook for responsive breakpoint detection
  - Create useLongPress hook for touch gesture context menus
  - Update Button component with 44px min-height for WCAG touch compliance
  - Update Toggle component with larger mobile size (44x28px)
  - Add responsive padding (p-4 md:p-8) to all pages
  - Update ArchivesPage with mobile menu button and horizontal scroll filters
  - Update SettingsPage with stacked column layout on mobile
  - Increase drag handle sizes for touch-friendly reordering
  - Add CSS utilities for touch-manipulation and safe-area insets
This commit is contained in:
maziggy
2025-12-09 12:55:59 +00:00
parent b4007c9594
commit 42cdfdbd74
20 changed files with 8857 additions and 8682 deletions
+3 -3
View File
@@ -26,9 +26,9 @@ export function Button({
};
const sizes = {
sm: 'px-3 py-1.5 text-sm gap-1.5',
md: 'px-4 py-2 text-sm gap-2',
lg: 'px-6 py-3 text-base gap-2',
sm: 'px-3 py-1.5 text-sm gap-1.5 min-h-[44px] md:min-h-0',
md: 'px-4 py-2 text-sm gap-2 min-h-[44px] md:min-h-0',
lg: 'px-6 py-3 text-base gap-2 min-h-[48px] md:min-h-0',
};
return (
+1 -1
View File
@@ -93,7 +93,7 @@ function SortableWidget({
className="cursor-grab active:cursor-grabbing p-1 hover:bg-bambu-dark-tertiary rounded transition-colors"
title="Drag to reorder"
>
<GripVertical className="w-4 h-4 text-bambu-gray" />
<GripVertical className="w-6 h-6 md:w-4 md:h-4 text-bambu-gray" />
</button>
<h3 className="text-sm font-medium text-white">{title}</h3>
</div>
@@ -117,7 +117,7 @@ export function ExternalLinksSettings() {
draggedId === link.id ? 'opacity-50' : ''
}`}
>
<GripVertical className="w-4 h-4 text-bambu-gray cursor-grab flex-shrink-0" />
<GripVertical className="w-6 h-6 md:w-4 md:h-4 text-bambu-gray cursor-grab flex-shrink-0" />
<div className="p-2 rounded-lg bg-bambu-dark-tertiary text-bambu-gray">
<Icon className="w-4 h-4" />
</div>
+74 -30
View File
@@ -1,12 +1,13 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import { NavLink, Outlet, useNavigate, useLocation } from 'react-router-dom';
import { Printer, Archive, Calendar, BarChart3, Cloud, Settings, Sun, Moon, ChevronLeft, ChevronRight, Keyboard, Github, GripVertical, ArrowUpCircle, Wrench, X, type LucideIcon } from 'lucide-react';
import { Printer, Archive, Calendar, BarChart3, Cloud, Settings, Sun, Moon, ChevronLeft, ChevronRight, Keyboard, Github, GripVertical, ArrowUpCircle, Wrench, X, Menu, type LucideIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useTheme } from '../contexts/ThemeContext';
import { KeyboardShortcutsModal } from './KeyboardShortcutsModal';
import { useQuery } from '@tanstack/react-query';
import { api } from '../api/client';
import { getIconByName } from './IconPicker';
import { useIsMobile } from '../hooks/useIsMobile';
interface NavItem {
id: string;
@@ -63,10 +64,12 @@ export function Layout() {
const location = useLocation();
const { theme, toggleTheme } = useTheme();
const { t } = useTranslation();
const isMobile = useIsMobile();
const [sidebarExpanded, setSidebarExpanded] = useState(() => {
const stored = localStorage.getItem('sidebarExpanded');
return stored !== 'false';
});
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [showShortcuts, setShowShortcuts] = useState(false);
const [sidebarOrder, setSidebarOrder] = useState<string[]>(getSidebarOrder);
const [draggedId, setDraggedId] = useState<string | null>(null);
@@ -219,6 +222,13 @@ export function Layout() {
localStorage.setItem('sidebarExpanded', String(sidebarExpanded));
}, [sidebarExpanded]);
// Close mobile drawer on navigation
useEffect(() => {
if (isMobile) {
setMobileDrawerOpen(false);
}
}, [location.pathname, isMobile]);
// Global keyboard shortcuts for navigation
const handleKeyDown = useCallback((e: KeyboardEvent) => {
const target = e.target as HTMLElement;
@@ -259,16 +269,46 @@ export function Layout() {
return (
<div className="flex min-h-screen">
{/* Sidebar */}
<aside
className={`${sidebarExpanded ? 'w-64' : 'w-16'} bg-bambu-dark-secondary border-r border-bambu-dark-tertiary flex flex-col fixed inset-y-0 left-0 z-30 transition-all duration-300`}
>
{/* Logo */}
<div className={`border-b border-bambu-dark-tertiary flex items-center justify-center ${sidebarExpanded ? 'p-4' : 'p-2'}`}>
{/* Mobile Header */}
{isMobile && (
<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
onClick={() => setMobileDrawerOpen(true)}
className="p-2 -ml-2 rounded-lg hover:bg-bambu-dark-tertiary transition-colors"
aria-label="Open menu"
>
<Menu className="w-6 h-6 text-white" />
</button>
<img
src={theme === 'dark' ? '/img/bambuddy_logo_dark.png' : '/img/bambuddy_logo_light.png'}
alt="Bambuddy"
className={sidebarExpanded ? 'h-16 w-auto' : 'h-8 w-8 object-cover object-left'}
className="h-8 ml-3"
/>
</header>
)}
{/* Mobile Drawer Backdrop */}
{isMobile && mobileDrawerOpen && (
<div
className="fixed inset-0 bg-black/60 z-40 transition-opacity"
onClick={() => setMobileDrawerOpen(false)}
/>
)}
{/* Sidebar / Mobile Drawer */}
<aside
className={`bg-bambu-dark-secondary border-r border-bambu-dark-tertiary flex flex-col transition-all duration-300 ${
isMobile
? `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'}`
}`}
>
{/* Logo */}
<div className={`border-b border-bambu-dark-tertiary flex items-center justify-center ${isMobile || sidebarExpanded ? 'p-4' : 'p-2'}`}>
<img
src={theme === 'dark' ? '/img/bambuddy_logo_dark.png' : '/img/bambuddy_logo_light.png'}
alt="Bambuddy"
className={isMobile || sidebarExpanded ? 'h-16 w-auto' : 'h-8 w-8 object-cover object-left'}
/>
</div>
@@ -304,15 +344,15 @@ export function Layout() {
<NavLink
to={`/external/${link.id}`}
className={({ isActive }) =>
`flex items-center ${sidebarExpanded ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition-colors group ${
`flex items-center ${isMobile || sidebarExpanded ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition-colors group ${
isActive
? 'bg-bambu-green text-white'
: 'text-bambu-gray-light hover:bg-bambu-dark-tertiary hover:text-white'
}`
}
title={!sidebarExpanded ? link.name : undefined}
title={!isMobile && !sidebarExpanded ? link.name : undefined}
>
{sidebarExpanded && (
{sidebarExpanded && !isMobile && (
<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 ? (
@@ -324,7 +364,7 @@ export function Layout() {
) : (
LinkIcon && <LinkIcon className="w-5 h-5 flex-shrink-0" />
)}
{sidebarExpanded && <span>{link.name}</span>}
{(isMobile || sidebarExpanded) && <span>{link.name}</span>}
</NavLink>
</li>
);
@@ -354,19 +394,19 @@ export function Layout() {
<NavLink
to={to}
className={({ isActive }) =>
`flex items-center ${sidebarExpanded ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition-colors group ${
`flex items-center ${isMobile || sidebarExpanded ? 'gap-3 px-4' : 'justify-center px-2'} py-3 rounded-lg transition-colors group ${
isActive
? 'bg-bambu-green text-white'
: 'text-bambu-gray-light hover:bg-bambu-dark-tertiary hover:text-white'
}`
}
title={!sidebarExpanded ? t(labelKey) : undefined}
title={!isMobile && !sidebarExpanded ? t(labelKey) : undefined}
>
{sidebarExpanded && (
{sidebarExpanded && !isMobile && (
<GripVertical className="w-4 h-4 flex-shrink-0 opacity-0 group-hover:opacity-50 cursor-grab active:cursor-grabbing -ml-1" />
)}
<Icon className="w-5 h-5 flex-shrink-0" />
{sidebarExpanded && <span>{t(labelKey)}</span>}
{(isMobile || sidebarExpanded) && <span>{t(labelKey)}</span>}
</NavLink>
</li>
);
@@ -375,22 +415,24 @@ export function Layout() {
</ul>
</nav>
{/* Collapse toggle */}
<button
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"
title={sidebarExpanded ? t('nav.collapseSidebar') : t('nav.expandSidebar')}
>
{sidebarExpanded ? (
<ChevronLeft className="w-5 h-5" />
) : (
<ChevronRight className="w-5 h-5" />
)}
</button>
{/* Collapse toggle - hide on mobile */}
{!isMobile && (
<button
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"
title={sidebarExpanded ? t('nav.collapseSidebar') : t('nav.expandSidebar')}
>
{sidebarExpanded ? (
<ChevronLeft className="w-5 h-5" />
) : (
<ChevronRight className="w-5 h-5" />
)}
</button>
)}
{/* Footer */}
<div className="p-2 border-t border-bambu-dark-tertiary">
{sidebarExpanded ? (
{isMobile || sidebarExpanded ? (
<div className="flex items-center justify-between px-2">
<div className="flex items-center gap-2">
<span className="text-sm text-bambu-gray">v{versionInfo?.version || '...'}</span>
@@ -471,7 +513,9 @@ export function Layout() {
</aside>
{/* Main content */}
<main className={`flex-1 bg-bambu-dark overflow-auto ${sidebarExpanded ? 'ml-64' : 'ml-16'} 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'
}`}>
{/* Persistent update banner */}
{showUpdateBanner && (
<div className="bg-bambu-green/20 border-b border-bambu-green/30 px-4 py-2 flex items-center justify-between">
+2 -2
View File
@@ -20,7 +20,7 @@ export function Toggle({ checked, onChange, disabled }: ToggleProps) {
aria-checked={checked}
disabled={disabled}
onClick={handleClick}
className={`relative inline-flex w-9 h-5 rounded-full transition-colors flex-shrink-0 focus:outline-none focus:ring-2 focus:ring-bambu-green focus:ring-offset-2 focus:ring-offset-bambu-dark ${
className={`relative inline-flex w-11 h-7 md:w-9 md:h-5 rounded-full transition-colors flex-shrink-0 focus:outline-none focus:ring-2 focus:ring-bambu-green focus:ring-offset-2 focus:ring-offset-bambu-dark ${
disabled
? 'bg-bambu-dark-tertiary/50 cursor-not-allowed opacity-50'
: checked
@@ -29,7 +29,7 @@ export function Toggle({ checked, onChange, disabled }: ToggleProps) {
}`}
>
<span
className={`pointer-events-none absolute top-[2px] left-[2px] w-4 h-4 bg-white rounded-full shadow transition-transform duration-200 ease-in-out ${
className={`pointer-events-none absolute top-[3px] md:top-[2px] left-[3px] md:left-[2px] w-5 h-5 md:w-4 md:h-4 bg-white rounded-full shadow transition-transform duration-200 ease-in-out ${
checked ? 'translate-x-4' : 'translate-x-0'
}`}
/>
+26
View File
@@ -0,0 +1,26 @@
import { useState, useEffect } from 'react';
const MOBILE_BREAKPOINT = 768; // md breakpoint
export function useIsMobile(): boolean {
const [isMobile, setIsMobile] = useState(() =>
typeof window !== 'undefined' ? window.innerWidth < MOBILE_BREAKPOINT : false
);
useEffect(() => {
const mediaQuery = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const handleChange = (e: MediaQueryListEvent) => {
setIsMobile(e.matches);
};
// Set initial value
setIsMobile(mediaQuery.matches);
// Modern browsers support addEventListener
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, []);
return isMobile;
}
+46
View File
@@ -0,0 +1,46 @@
import { useCallback, useRef } from 'react';
interface LongPressOptions {
onLongPress: (e: React.TouchEvent | React.MouseEvent) => void;
onClick?: () => void;
delay?: number;
}
export function useLongPress({ onLongPress, onClick, delay = 500 }: LongPressOptions) {
const timeoutRef = useRef<number | null>(null);
const targetRef = useRef<EventTarget | null>(null);
const longPressTriggered = useRef(false);
const start = useCallback(
(e: React.TouchEvent | React.MouseEvent) => {
longPressTriggered.current = false;
targetRef.current = e.target;
timeoutRef.current = window.setTimeout(() => {
longPressTriggered.current = true;
onLongPress(e);
}, delay);
},
[onLongPress, delay]
);
const clear = useCallback(
(e: React.TouchEvent | React.MouseEvent, shouldTriggerClick = true) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
if (shouldTriggerClick && !longPressTriggered.current && onClick && targetRef.current === e.target) {
onClick();
}
},
[onClick]
);
return {
onMouseDown: start,
onMouseUp: (e: React.MouseEvent) => clear(e, true),
onMouseLeave: (e: React.MouseEvent) => clear(e, false),
onTouchStart: start,
onTouchEnd: (e: React.TouchEvent) => clear(e, true),
};
}
+38
View File
@@ -143,3 +143,41 @@ body {
#333 4px
);
}
/* Touch manipulation to prevent zoom on double-tap */
.touch-manipulation {
touch-action: manipulation;
}
/* Safe area insets for notched devices */
.safe-area-bottom {
padding-bottom: env(safe-area-inset-bottom, 0);
}
.safe-area-top {
padding-top: env(safe-area-inset-top, 0);
}
/* Hide scrollbar but keep functionality */
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
/* Mobile drawer animation */
@keyframes slide-in-left {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
.animate-slide-in-left {
animation: slide-in-left 0.3s ease-out;
}
+37 -16
View File
@@ -35,8 +35,10 @@ import {
Camera,
FileText,
FileCode,
MoreVertical,
} from 'lucide-react';
import { api } from '../api/client';
import { useIsMobile } from '../hooks/useIsMobile';
import type { Archive } from '../api/client';
import { Card, CardContent } from '../components/Card';
import { Button } from '../components/Button';
@@ -93,6 +95,7 @@ function ArchiveCard({
}) {
const queryClient = useQueryClient();
const { showToast } = useToast();
const isMobile = useIsMobile();
const [showViewer, setShowViewer] = useState(false);
const [showReprint, setShowReprint] = useState(false);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
@@ -355,6 +358,20 @@ function ArchiveCard({
<Image className="w-12 h-12 text-bambu-dark-tertiary" />
</div>
)}
{/* Mobile menu button */}
{isMobile && (
<button
className="absolute top-2 right-10 p-1.5 rounded bg-black/50 hover:bg-black/70 transition-colors"
onClick={(e) => {
e.stopPropagation();
const rect = e.currentTarget.getBoundingClientRect();
setContextMenu({ x: rect.left, y: rect.bottom + 4 });
}}
title="Menu"
>
<MoreVertical className="w-5 h-5 text-white" />
</button>
)}
{/* Favorite star */}
<button
className="absolute top-2 right-2 p-1 rounded bg-black/50 hover:bg-black/70 transition-colors"
@@ -1069,7 +1086,7 @@ export function ArchivesPage() {
return (
<div
className="p-8 relative min-h-full"
className="p-4 md:p-8 relative min-h-full"
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
@@ -1175,20 +1192,23 @@ export function ArchivesPage() {
{/* Filters */}
<Card className="mb-6">
<CardContent className="py-4">
<div className="flex gap-4 items-center flex-wrap">
<div className="flex-1 relative min-w-[200px]">
<div className="flex flex-col md:flex-row gap-3 md:gap-4 md:items-center md:flex-wrap">
{/* Search - full width on mobile */}
<div className="w-full md:flex-1 relative md:min-w-[200px]">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-bambu-gray" />
<input
ref={searchInputRef}
type="text"
placeholder="Search archives... (press /)"
className="w-full pl-10 pr-4 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
placeholder="Search archives..."
className="w-full pl-10 pr-4 py-3 md:py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
<div className="flex items-center gap-2">
<Filter className="w-4 h-4 text-bambu-gray" />
{/* Filters - horizontal scroll on mobile */}
<div className="flex gap-2 md:gap-4 overflow-x-auto pb-1 md:pb-0 -mx-4 px-4 md:mx-0 md:px-0 md:flex-wrap scrollbar-hide">
<div className="flex items-center gap-2 flex-shrink-0">
<Filter className="w-4 h-4 text-bambu-gray hidden md:block" />
<select
className="px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
value={filterPrinter || ''}
@@ -1204,8 +1224,8 @@ export function ArchivesPage() {
))}
</select>
</div>
<div className="flex items-center gap-2">
<Package className="w-4 h-4 text-bambu-gray" />
<div className="flex items-center gap-2 flex-shrink-0">
<Package className="w-4 h-4 text-bambu-gray hidden md:block" />
<select
className="px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
value={filterMaterial || ''}
@@ -1223,7 +1243,7 @@ export function ArchivesPage() {
</div>
<button
onClick={() => setFilterFavorites(!filterFavorites)}
className={`flex items-center gap-2 px-3 py-2 rounded-lg border transition-colors ${
className={`flex items-center gap-2 px-3 py-2 rounded-lg border transition-colors flex-shrink-0 ${
filterFavorites
? 'bg-yellow-500/20 border-yellow-500 text-yellow-400'
: 'bg-bambu-dark border-bambu-dark-tertiary text-bambu-gray hover:text-white'
@@ -1231,11 +1251,11 @@ export function ArchivesPage() {
title={filterFavorites ? 'Show all' : 'Show favorites only'}
>
<Star className={`w-4 h-4 ${filterFavorites ? 'fill-yellow-400' : ''}`} />
<span className="text-sm">Favorites</span>
<span className="text-sm hidden md:inline">Favorites</span>
</button>
{uniqueTags.length > 0 && (
<div className="flex items-center gap-2">
<Tag className="w-4 h-4 text-bambu-gray" />
<div className="flex items-center gap-2 flex-shrink-0">
<Tag className="w-4 h-4 text-bambu-gray hidden md:block" />
<select
className="px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
value={filterTag || ''}
@@ -1250,8 +1270,8 @@ export function ArchivesPage() {
</select>
</div>
)}
<div className="flex items-center gap-2">
<ArrowUpDown className="w-4 h-4 text-bambu-gray" />
<div className="flex items-center gap-2 flex-shrink-0">
<ArrowUpDown className="w-4 h-4 text-bambu-gray hidden md:block" />
<select
className="px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
value={sortBy}
@@ -1265,7 +1285,7 @@ export function ArchivesPage() {
<option value="size-asc">Smallest first</option>
</select>
</div>
<div className="flex items-center border border-bambu-dark-tertiary rounded-lg overflow-hidden">
<div className="flex items-center border border-bambu-dark-tertiary rounded-lg overflow-hidden flex-shrink-0">
<button
className={`p-2 ${viewMode === 'grid' ? 'bg-bambu-green text-white' : 'bg-bambu-dark text-bambu-gray hover:text-white'}`}
onClick={() => setViewMode('grid')}
@@ -1288,6 +1308,7 @@ export function ArchivesPage() {
<CalendarDays className="w-4 h-4" />
</button>
</div>
</div>
{hasTopFilters && (
<Button
variant="ghost"
+2 -2
View File
@@ -914,7 +914,7 @@ export function MaintenancePage() {
if (isLoading) {
return (
<div className="p-8 flex justify-center">
<div className="p-4 md:p-8 flex justify-center">
<Loader2 className="w-8 h-8 text-bambu-green animate-spin" />
</div>
);
@@ -924,7 +924,7 @@ export function MaintenancePage() {
const totalWarning = overview?.reduce((sum, p) => sum + p.warning_count, 0) || 0;
return (
<div className="p-8">
<div className="p-4 md:p-8">
{/* Header */}
<div className="mb-6">
<h1 className="text-2xl font-bold text-white">Maintenance</h1>
+1 -1
View File
@@ -1819,7 +1819,7 @@ export function PrintersPage() {
}, [sortBy, sortedPrinters]);
return (
<div className="p-8">
<div className="p-4 md:p-8">
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-2xl font-bold text-white">Printers</h1>
+1 -1
View File
@@ -2788,7 +2788,7 @@ export function ProfilesPage() {
if (statusLoading) {
return (
<div className="p-8 flex items-center justify-center min-h-[400px]">
<div className="p-4 md:p-8 flex items-center justify-center min-h-[400px]">
<Loader2 className="w-8 h-8 text-bambu-green animate-spin" />
</div>
);
+3 -3
View File
@@ -144,9 +144,9 @@ function SortableQueueItem({
<div
{...attributes}
{...listeners}
className="flex items-center justify-center w-8 h-8 rounded-lg bg-bambu-dark cursor-grab active:cursor-grabbing hover:bg-bambu-dark-tertiary transition-colors"
className="flex items-center justify-center w-10 h-10 md:w-8 md:h-8 rounded-lg bg-bambu-dark cursor-grab active:cursor-grabbing hover:bg-bambu-dark-tertiary transition-colors touch-manipulation"
>
<GripVertical className="w-4 h-4 text-bambu-gray" />
<GripVertical className="w-6 h-6 md:w-4 md:h-4 text-bambu-gray" />
</div>
) : position !== undefined ? (
<div className="flex items-center justify-center w-8 h-8 rounded-lg bg-bambu-dark text-bambu-gray text-sm font-medium">
@@ -505,7 +505,7 @@ export function QueuePage() {
};
return (
<div className="p-8">
<div className="p-4 md:p-8">
{/* Header */}
<div className="flex items-center justify-between mb-8">
<div>
+6 -6
View File
@@ -305,14 +305,14 @@ export function SettingsPage() {
if (isLoading || !localSettings) {
return (
<div className="p-8 flex justify-center">
<div className="p-4 md:p-8 flex justify-center">
<Loader2 className="w-8 h-8 text-bambu-green animate-spin" />
</div>
);
}
return (
<div className="p-8">
<div className="p-4 md:p-8">
<div className="mb-8">
<h1 className="text-2xl font-bold text-white">Settings</h1>
<p className="text-bambu-gray">Configure Bambuddy</p>
@@ -366,9 +366,9 @@ export function SettingsPage() {
{/* General Tab */}
{activeTab === 'general' && (
<div className="flex gap-8">
<div className="flex flex-col lg:flex-row gap-6 lg:gap-8">
{/* Left Column - General Settings */}
<div className="space-y-6 flex-1 max-w-xl">
<div className="space-y-6 flex-1 lg:max-w-xl">
<Card>
<CardHeader>
<h2 className="text-lg font-semibold text-white">{t('settings.general')}</h2>
@@ -630,7 +630,7 @@ export function SettingsPage() {
</div>
{/* Second Column - AMS & Spoolman */}
<div className="space-y-6 flex-1 max-w-md">
<div className="space-y-6 flex-1 lg:max-w-md">
<Card>
<CardHeader>
<h2 className="text-lg font-semibold text-white">AMS Display Thresholds</h2>
@@ -740,7 +740,7 @@ export function SettingsPage() {
</div>
{/* Third Column - Updates */}
<div className="space-y-6 flex-1 max-w-sm">
<div className="space-y-6 flex-1 lg:max-w-sm">
<Card>
<CardHeader>
<h2 className="text-lg font-semibold text-white">Updates</h2>
+2 -2
View File
@@ -338,7 +338,7 @@ export function StatsPage() {
if (isLoading) {
return (
<div className="p-8">
<div className="p-4 md:p-8">
<div className="text-center py-12 text-bambu-gray">Loading statistics...</div>
</div>
);
@@ -392,7 +392,7 @@ export function StatsPage() {
];
return (
<div className="p-8">
<div className="p-4 md:p-8">
<div className="mb-6">
<h1 className="text-2xl font-bold text-white">Dashboard</h1>
<p className="text-bambu-gray">Drag widgets to rearrange. Click the eye icon to hide.</p>