* 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>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/img/apple-touch-icon.png" />
<script type="module" crossorigin src="/assets/index-DHVVjLkT.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CcC-KyfM.css">
<script type="module" crossorigin src="/assets/index-iXqNZkoR.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BDGCrNHI.css">
</head>
<body>
<div id="root"></div>