mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 19:21:33 +02:00
Update responsive breakpoints for various components, implement useIsSidebarCompact hook
This commit is contained in:
@@ -187,7 +187,7 @@ export function AMSHistoryModal({
|
||||
{/* Content */}
|
||||
<div className="p-6 space-y-6 overflow-y-auto max-h-[calc(90vh-80px)]">
|
||||
{/* 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 }}>
|
||||
<button
|
||||
onClick={() => setMode('humidity')}
|
||||
@@ -228,10 +228,10 @@ export function AMSHistoryModal({
|
||||
</div>
|
||||
|
||||
{/* 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' ? (
|
||||
<>
|
||||
<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>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-2xl font-bold" style={{ color: getHumidityColor(currentHumidity) }}>
|
||||
@@ -240,19 +240,19 @@ export function AMSHistoryModal({
|
||||
<TrendIcon trend={humidityTrend} />
|
||||
</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-2xl font-bold" style={{ color: textPrimary }}>
|
||||
{data?.avg_humidity != null ? `${data.avg_humidity}%` : '—'}
|
||||
</p>
|
||||
</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-2xl font-bold text-green-500">
|
||||
{data?.min_humidity != null ? `${data.min_humidity}%` : '—'}
|
||||
</p>
|
||||
</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-2xl font-bold text-red-500">
|
||||
{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>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-2xl font-bold" style={{ color: getTempColor(currentTemp) }}>
|
||||
@@ -270,19 +270,19 @@ export function AMSHistoryModal({
|
||||
<TrendIcon trend={tempTrend} />
|
||||
</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-2xl font-bold" style={{ color: textPrimary }}>
|
||||
{data?.avg_temperature != null ? `${data.avg_temperature}°C` : '—'}
|
||||
</p>
|
||||
</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-2xl font-bold text-blue-500">
|
||||
{data?.min_temperature != null ? `${data.min_temperature}°C` : '—'}
|
||||
</p>
|
||||
</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-2xl font-bold text-red-500">
|
||||
{data?.max_temperature != null ? `${data.max_temperature}°C` : '—'}
|
||||
|
||||
@@ -178,7 +178,7 @@ export function Dashboard({ widgets, storageKey, columns = 4, stackBelow, hideCo
|
||||
if (!stackBelow) return undefined;
|
||||
const mediaQuery = window.matchMedia(`(max-width: ${stackBelow}px)`);
|
||||
const handleChange = (event: MediaQueryListEvent | MediaQueryList) => {
|
||||
setIsStacked('matches' in event ? event.matches : event.matches);
|
||||
setIsStacked(event.matches);
|
||||
};
|
||||
handleChange(mediaQuery);
|
||||
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))`,
|
||||
}}
|
||||
>
|
||||
{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>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
||||
@@ -157,7 +157,7 @@ export function FilamentTrends({ archives, currency = '$' }: FilamentTrendsProps
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 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>
|
||||
<div className="flex gap-1 bg-bambu-dark rounded-lg p-1">
|
||||
{(['7d', '30d', '90d', '365d', 'all'] as TimeRange[]).map((range) => (
|
||||
@@ -177,7 +177,7 @@ export function FilamentTrends({ archives, currency = '$' }: FilamentTrendsProps
|
||||
</div>
|
||||
|
||||
{/* 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="flex items-center justify-between gap-3">
|
||||
<p className="text-sm text-bambu-gray leading-none">Period Filament</p>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { SwitchbarPopover } from './SwitchbarPopover';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { api, supportApi, pendingUploadsApi } from '../api/client';
|
||||
import { getIconByName } from './IconPicker';
|
||||
import { useIsMobile } from '../hooks/useIsMobile';
|
||||
import { useIsSidebarCompact } from '../hooks/useIsSidebarCompact';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { useToast } from '../contexts/ToastContext';
|
||||
import { Card, CardHeader, CardContent } from './Card';
|
||||
@@ -72,7 +72,7 @@ export function Layout() {
|
||||
const location = useLocation();
|
||||
const { mode, toggleMode } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const isMobile = useIsMobile();
|
||||
const isSidebarCompact = useIsSidebarCompact();
|
||||
const { user, authEnabled, logout, hasPermission } = useAuth();
|
||||
const { showToast } = useToast();
|
||||
const [showChangePasswordModal, setShowChangePasswordModal] = useState(false);
|
||||
@@ -311,12 +311,12 @@ export function Layout() {
|
||||
localStorage.setItem('sidebarExpanded', String(sidebarExpanded));
|
||||
}, [sidebarExpanded]);
|
||||
|
||||
// Close mobile drawer on navigation
|
||||
// Close compact drawer on navigation
|
||||
useEffect(() => {
|
||||
if (isMobile) {
|
||||
if (isSidebarCompact) {
|
||||
setMobileDrawerOpen(false);
|
||||
}
|
||||
}, [location.pathname, isMobile]);
|
||||
}, [location.pathname, isSidebarCompact]);
|
||||
|
||||
// Listen for plate detection warnings (objects on plate, print paused)
|
||||
// Only show to users with printers:control permission
|
||||
@@ -390,8 +390,8 @@ export function Layout() {
|
||||
|
||||
return (
|
||||
<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">
|
||||
<button
|
||||
onClick={() => setMobileDrawerOpen(true)}
|
||||
@@ -408,8 +408,8 @@ export function Layout() {
|
||||
</header>
|
||||
)}
|
||||
|
||||
{/* Mobile Drawer Backdrop */}
|
||||
{isMobile && mobileDrawerOpen && (
|
||||
{/* Compact Drawer Backdrop */}
|
||||
{isSidebarCompact && mobileDrawerOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/60 z-40 transition-opacity"
|
||||
onClick={() => setMobileDrawerOpen(false)}
|
||||
@@ -419,17 +419,17 @@ export function Layout() {
|
||||
{/* Sidebar / Mobile Drawer */}
|
||||
<aside
|
||||
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-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'}`}>
|
||||
<div className={`border-b border-bambu-dark-tertiary flex items-center justify-center ${isSidebarCompact || sidebarExpanded ? 'p-4' : 'p-2'}`}>
|
||||
<img
|
||||
src={mode === 'dark' ? '/img/bambuddy_logo_dark_transparent.png' : '/img/bambuddy_logo_light.png'}
|
||||
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>
|
||||
|
||||
@@ -467,10 +467,10 @@ export function Layout() {
|
||||
href={link.url}
|
||||
target="_blank"
|
||||
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" />
|
||||
)}
|
||||
{link.custom_icon ? (
|
||||
@@ -482,21 +482,21 @@ export function Layout() {
|
||||
) : (
|
||||
LinkIcon && <LinkIcon className="w-5 h-5 flex-shrink-0" />
|
||||
)}
|
||||
{(isMobile || sidebarExpanded) && <span>{link.name}</span>}
|
||||
{(isSidebarCompact || sidebarExpanded) && <span>{link.name}</span>}
|
||||
</a>
|
||||
) : (
|
||||
<NavLink
|
||||
to={`/external/${link.id}`}
|
||||
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
|
||||
? 'bg-bambu-green 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" />
|
||||
)}
|
||||
{link.custom_icon ? (
|
||||
@@ -508,7 +508,7 @@ export function Layout() {
|
||||
) : (
|
||||
LinkIcon && <LinkIcon className="w-5 h-5 flex-shrink-0" />
|
||||
)}
|
||||
{(isMobile || sidebarExpanded) && <span>{link.name}</span>}
|
||||
{(isSidebarCompact || sidebarExpanded) && <span>{link.name}</span>}
|
||||
</NavLink>
|
||||
)}
|
||||
</li>
|
||||
@@ -544,15 +544,15 @@ export function Layout() {
|
||||
<NavLink
|
||||
to={to}
|
||||
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
|
||||
? 'bg-bambu-green 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" />
|
||||
)}
|
||||
<div className="relative">
|
||||
@@ -565,7 +565,7 @@ export function Layout() {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{(isMobile || sidebarExpanded) && <span>{t(labelKey)}</span>}
|
||||
{(isSidebarCompact || sidebarExpanded) && <span>{t(labelKey)}</span>}
|
||||
</NavLink>
|
||||
</li>
|
||||
);
|
||||
@@ -574,8 +574,8 @@ export function Layout() {
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{/* Collapse toggle - hide on mobile */}
|
||||
{!isMobile && (
|
||||
{/* Collapse toggle - hide on compact sidebar */}
|
||||
{!isSidebarCompact && (
|
||||
<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"
|
||||
@@ -591,7 +591,7 @@ export function Layout() {
|
||||
|
||||
{/* Footer */}
|
||||
<div className="p-2 border-t border-bambu-dark-tertiary">
|
||||
{isMobile || sidebarExpanded ? (
|
||||
{isSidebarCompact || sidebarExpanded ? (
|
||||
<div className="flex flex-col gap-2 px-2">
|
||||
{/* Top row: icons */}
|
||||
<div className="flex items-center justify-center gap-1">
|
||||
@@ -783,7 +783,7 @@ export function Layout() {
|
||||
|
||||
{/* Main content */}
|
||||
<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 */}
|
||||
{debugLoggingState?.enabled && (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
const MOBILE_BREAKPOINT = 1144; // hide sidebar below this width
|
||||
const MOBILE_BREAKPOINT = 768;
|
||||
|
||||
export function useIsMobile(): boolean {
|
||||
const [isMobile, setIsMobile] = useState(() =>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -351,7 +351,7 @@ function MaintenanceCard({
|
||||
</div>
|
||||
|
||||
{/* 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}>
|
||||
<Toggle
|
||||
checked={item.enabled}
|
||||
|
||||
@@ -2626,9 +2626,9 @@ function PrinterCard({
|
||||
<div className="flex-1 h-px bg-bambu-dark-tertiary/30" />
|
||||
</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 */}
|
||||
<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 */}
|
||||
<div
|
||||
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>
|
||||
|
||||
{/* 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 */}
|
||||
<button
|
||||
onClick={() => setShowStopConfirm(true)}
|
||||
@@ -2750,7 +2750,7 @@ function PrinterCard({
|
||||
)}
|
||||
</div>
|
||||
{(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 && (
|
||||
<HumidityIndicator
|
||||
humidity={ams.humidity}
|
||||
@@ -3104,9 +3104,9 @@ function PrinterCard({
|
||||
)}
|
||||
</div>
|
||||
{/* 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 */}
|
||||
<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 */}
|
||||
{isHtRefreshing && (
|
||||
<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>
|
||||
{/* Stats stacked vertically: Temp on top, Humidity below */}
|
||||
{(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 && (
|
||||
<TemperatureIndicator
|
||||
temp={ams.temp}
|
||||
|
||||
@@ -262,7 +262,7 @@ function LoginForm({ onSuccess, t }: { onSuccess: () => void; t: TFunction }) {
|
||||
</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' && (
|
||||
<Button type="button" variant="secondary" onClick={() => setStep('email')} className="flex-1">
|
||||
{t('profiles.login.back')}
|
||||
@@ -1815,7 +1815,7 @@ function CreatePresetModal({
|
||||
</div>
|
||||
|
||||
{/* 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
|
||||
onClick={() => setActiveTab('common')}
|
||||
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
|
||||
{jsonError && <AlertCircle className="w-3 h-3 text-red-400" />}
|
||||
</button>
|
||||
<div className="flex-1 max-[565px]:hidden" />
|
||||
<div className="flex-1 max-[640px]:hidden" />
|
||||
<button
|
||||
onClick={() => {
|
||||
const exportData = {
|
||||
@@ -2010,9 +2010,9 @@ function CreatePresetModal({
|
||||
<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">
|
||||
{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>
|
||||
<div className="w-48 max-[633px]:w-full">{renderFieldInput(field)}</div>
|
||||
<div className="w-48 max-[640px]:w-full">{renderFieldInput(field)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -2460,7 +2460,7 @@ function CloudProfilesView({
|
||||
/>
|
||||
</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
|
||||
variant={compareMode ? 'primary' : 'secondary'}
|
||||
onClick={() => {
|
||||
|
||||
@@ -760,7 +760,7 @@ export function StatsPage() {
|
||||
key={dashboardKey}
|
||||
widgets={widgets}
|
||||
storageKey="bambusy-dashboard-layout"
|
||||
stackBelow={766}
|
||||
stackBelow={640}
|
||||
hideControls
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user