Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
5.0 kB ยท 158 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159"use client";
import { useCallback, useRef, useState } from "react";
/** * Mapping of data-slot values to component names for display */const DATA_SLOT_TO_COMPONENT: Record<string, string> = { // Layout components status: "Status", "status-header": "StatusHeader", "status-title": "StatusTitle", "status-description": "StatusDescription", "status-content": "StatusContent",
// Banner components "status-banner": "StatusBanner",
// Monitor components "status-component": "StatusComponent", "status-component-header": "StatusComponentHeader", "status-component-header-left": "StatusComponentHeaderLeft", "status-component-header-right": "StatusComponentHeaderRight", "status-component-body": "StatusComponentBody", "status-component-title": "StatusComponentTitle", "status-component-uptime": "StatusComponentUptime", "status-component-status": "StatusComponentStatus", "status-component-footer": "StatusComponentFooter",
// Group component "status-component-group": "StatusComponentGroup", "status-component-group-trigger": "StatusComponentGroupTrigger", "status-component-group-content": "StatusComponentGroupContent",
// Feed component "status-feed": "StatusFeed",
// Event components "status-event-group": "StatusEventGroup", "status-event": "StatusEvent", "status-event-content": "StatusEventContent", "status-event-title": "StatusEventTitle", "status-event-title-check": "StatusEventTitleCheck", "status-event-affected": "StatusEventAffected", "status-event-affected-badge": "StatusEventAffectedBadge", "status-event-date": "StatusEventDate", "status-event-date-badge": "StatusEventDateBadge", "status-event-aside": "StatusEventAside", "status-event-timeline-report": "StatusEventTimelineReport", "status-event-timeline-report-update": "StatusEventTimelineReportUpdate", "status-event-timeline-maintenance": "StatusEventTimelineMaintenance", "status-event-timeline-title": "StatusEventTimelineTitle", "status-event-timeline-message": "StatusEventTimelineMessage", "status-event-timeline-dot": "StatusEventTimelineDot", "status-event-timeline-separator": "StatusEventTimelineSeparator",
// Bar components "status-bar": "StatusBar", "status-bar-item": "StatusBarItem", "status-bar-card": "StatusBarCard", "status-bar-content": "StatusBarContent", "status-bar-event": "StatusBarEvent",
// Utility components "status-timestamp": "StatusTimestamp", "status-timestamp-content": "StatusTimestampContent", "status-timestamp-row": "StatusTimestampRow",};
/** * Find the nearest element with a data-slot attribute by traversing up the DOM tree */function findNearestSlot(target: HTMLElement, root: HTMLElement) { let el: HTMLElement | null = target; while (el && el !== root) { const slot = el.dataset.slot; if (slot) { const componentName = DATA_SLOT_TO_COMPONENT[slot] || slot; return { element: el, name: componentName }; } el = el.parentElement; } return null;}
/** * ComponentHighlighter * * Highlights OpenStatus components on hover by detecting their data-slot attributes. * Uses DOM inspection to find components, which works reliably at any nesting level. * * @param enabled - Control the highlighter state externally (optional) * @param children - The component tree to enable highlighting for * * @example * ```tsx * <ComponentHighlighter> * <StatusPageExample /> * </ComponentHighlighter> * ``` */export function ComponentHighlighter({ enabled = true, children,}: { enabled?: boolean; shortcut?: boolean; children: React.ReactNode;}) { const rootRef = useRef<HTMLDivElement>(null);
const [hovered, setHovered] = useState<{ rect: DOMRect; name: string; } | null>(null);
// Find and highlight the nearest component with data-slot on mouse move const handleMouseMove = useCallback( (e: React.MouseEvent) => { if (!enabled || !rootRef.current) return; const match = findNearestSlot(e.target as HTMLElement, rootRef.current); if (match) { setHovered({ rect: match.element.getBoundingClientRect(), name: match.name, }); } else { setHovered(null); } }, [enabled], );
return ( <div ref={rootRef} onMouseMove={enabled ? handleMouseMove : undefined} onMouseLeave={enabled ? () => setHovered(null) : undefined} > {children} {hovered && ( <div className="bg-info/5 outline-info pointer-events-none fixed z-[99999] outline-2 transition-all duration-100 ease-in-out" style={{ top: hovered.rect.top, left: hovered.rect.left, width: hovered.rect.width, height: hovered.rect.height, }} > <span className="bg-info absolute -top-px -left-px px-2 py-0.5 font-mono text-[11px] leading-tight font-semibold whitespace-nowrap text-white"> {hovered.name} </span> </div> )} </div> );}