From a67e19a2f8b1054af3a7d783914a1e5ffb7e9344 Mon Sep 17 00:00:00 2001 From: Richard Bergsma Date: Wed, 11 Jun 2025 22:55:58 +0200 Subject: [PATCH] Enhance UptimeStatusIsland component with mobile responsiveness and scroll tracking - Added state management for mobile detection and scroll position tracking in the UptimeStatusIsland component. - Implemented responsive design adjustments for heartbeat display based on screen size. - Introduced visual indicators for scroll position on mobile to improve user experience. - Updated styles for heartbeat history container to accommodate mobile layout and scrolling behavior. --- src/components/UptimeStatusIsland.jsx | 212 ++++++++++++++++++++------ 1 file changed, 168 insertions(+), 44 deletions(-) diff --git a/src/components/UptimeStatusIsland.jsx b/src/components/UptimeStatusIsland.jsx index b8a1602..3154d79 100644 --- a/src/components/UptimeStatusIsland.jsx +++ b/src/components/UptimeStatusIsland.jsx @@ -96,6 +96,9 @@ export default function UptimeStatusIsland() { const [userZone, setUserZone] = useState(null); const [userLocale, setUserLocale] = useState(null); const [secondsToNextUpdate, setSecondsToNextUpdate] = useState(null); + const [isMobile, setIsMobile] = useState(false); + const [showLeftShadow, setShowLeftShadow] = useState(false); + const heartbeatContainerRefs = useRef({}); useEffect(() => { setUserZone(Intl.DateTimeFormat().resolvedOptions().timeZone); @@ -260,6 +263,19 @@ export default function UptimeStatusIsland() { } } + useEffect(() => { + const checkMobile = () => setIsMobile(window.innerWidth < 640); + checkMobile(); + window.addEventListener('resize', checkMobile); + return () => window.removeEventListener('resize', checkMobile); + }, []); + + // Track scroll position for left shadow on mobile heartbeat bar + const handleHeartbeatScroll = (monitorId, e) => { + if (!isMobile) return; + setShowLeftShadow(e.target.scrollLeft > 0); + }; + return (
@@ -305,21 +321,17 @@ export default function UptimeStatusIsland() {
    {group.monitorList.map((monitor) => (
  • -
    -
    - - {monitor.name} -
    -
    - {monitor.certExpiryDaysRemaining !== undefined && ( - <> - - {getCertText(monitor.certExpiryDaysRemaining)} - + {isMobile ? ( + <> +
    + + {monitor.name} +
    +
    + {monitor.certExpiryDaysRemaining !== undefined && ( toggleBadge(monitor.id, 'cert')} style={{ cursor: 'pointer', position: 'relative' }} @@ -333,19 +345,11 @@ export default function UptimeStatusIsland() { )} - - )} -
    -
    - {monitor.avgPing !== undefined && ( - <> - - Avg: {monitor.avgPing} ms - + )} + {monitor.avgPing !== undefined && ( toggleBadge(monitor.id, 'avgPing')} style={{ cursor: 'pointer', position: 'relative' }} > @@ -356,18 +360,11 @@ export default function UptimeStatusIsland() { )} - - )} -
    -
    - {monitor.uptime24h !== undefined && ( - <> - - 24h: {monitor.uptime24h.toFixed(1)}% - + )} + {monitor.uptime24h !== undefined && ( toggleBadge(monitor.id, 'uptime')} style={{ cursor: 'pointer', position: 'relative' }} > @@ -378,17 +375,122 @@ export default function UptimeStatusIsland() { )} - - )} + )} +
    + + ) : ( +
    +
    + + {monitor.name} +
    +
    + {monitor.certExpiryDaysRemaining !== undefined && ( + <> + + {getCertText(monitor.certExpiryDaysRemaining)} + + toggleBadge(monitor.id, 'cert')} + style={{ cursor: 'pointer', position: 'relative' }} + > + + {openBadge[monitor.id]?.cert && ( + + {monitor.certExpiryDaysRemaining < 0 + ? 'Expired!' + : `Expires in ${monitor.certExpiryDaysRemaining} days`} + + )} + + + )} +
    +
    + {monitor.avgPing !== undefined && ( + <> + + Avg: {monitor.avgPing} ms + + toggleBadge(monitor.id, 'avgPing')} + style={{ cursor: 'pointer', position: 'relative' }} + > + + {openBadge[monitor.id]?.avgPing && ( + + {monitor.avgPing} ms + + )} + + + )} +
    +
    + {monitor.uptime24h !== undefined && ( + <> + + 24h: {monitor.uptime24h.toFixed(1)}% + + toggleBadge(monitor.id, 'uptime')} + style={{ cursor: 'pointer', position: 'relative' }} + > + + {openBadge[monitor.id]?.uptime && ( + + {monitor.uptime24h.toFixed(1)}% + + )} + + + )} +
    -
    + )} {monitor.heartbeatHistory && monitor.heartbeatHistory.length > 0 && ( -
    -
    - {Array.from({ length: 40 }).map((_, i) => { +
    { if (el && isMobile) heartbeatContainerRefs.current[monitor.id] = el; }} + onScroll={e => handleHeartbeatScroll(monitor.id, e)} + > + {/* Left shadow overlay for mobile, only if scrolled */} + {isMobile && showLeftShadow && ( +
    + )} +
    + {(() => { const hbArr = monitor.heartbeatHistory ?? []; - const hb = hbArr.slice(-40)[i]; - return ( + const slice = isMobile ? hbArr.slice(-10) : hbArr.slice(-40); + // Show most recent first (left), so reverse the slice + const ordered = slice; + return ordered.map((hb, i) => ( } @@ -402,8 +504,8 @@ export default function UptimeStatusIsland() { style={{ cursor: 'pointer' }} > - ); - })} + )); + })()}
    )} @@ -421,6 +523,28 @@ export default function UptimeStatusIsland() { display: grid; grid-template-columns: repeat(40, minmax(0, 1fr)); } + .grid-cols-10 { + display: grid; + grid-template-columns: repeat(10, 1fr); + } + .heartbeat-history-container.mobile { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + min-width: 0; + width: 100%; + position: relative; + max-width: 100vw; + } + .heartbeat-left-shadow { + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 24px; + pointer-events: none; + z-index: 10; + background: linear-gradient(to right, rgba(0,0,0,0.18) 0%, rgba(0,0,0,0.00) 100%); + } `}
    );