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.
18 kB ยท 617 lines
TSX
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618"use client";
import { useStatusBlocksLabels } from "@openstatus/ui/components/blocks/status-i18n";import { StatusIcon as UnifiedStatusIcon } from "@openstatus/ui/components/blocks/status-icon";import type { StatusBarData, StatusType,} from "@openstatus/ui/components/blocks/status.types";import { Skeleton } from "@openstatus/ui/components/ui/skeleton";import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger,} from "@openstatus/ui/components/ui/tooltip";import { useMediaQuery } from "@openstatus/ui/hooks/use-media-query";import { cn } from "@openstatus/ui/lib/utils";import { formatDistanceToNowStrict, subDays } from "date-fns";import { ArrowUpRight, InfoIcon } from "lucide-react";import { useState } from "react";
// ============================================================================// Layout Components// ============================================================================
interface StatusComponentProps extends React.ComponentProps<"div"> { variant: Exclude<StatusType, "empty">;}
/** * StatusComponent - Root container for individual monitor/service status displays * * This component serves as the main container for displaying a single monitor or * service status. It establishes the status type context via data-variant attribute, * which child components (like StatusComponentIcon and StatusComponentStatus) use * to display the appropriate colors and icons. * * The component acts as a CSS group (/component) for advanced selector patterns, * enabling child components to style themselves based on the parent's variant. * * @param variant - The status type (success, degraded, error, or info) * * @example * ```tsx * <StatusComponent variant="success"> * <StatusComponentHeader> * <StatusComponentHeaderLeft> * <StatusComponentIcon /> * <StatusComponentTitle>API Server</StatusComponentTitle> * <StatusComponentDescription>Main API endpoint</StatusComponentDescription> * </StatusComponentHeaderLeft> * <StatusComponentHeaderRight> * <StatusComponentUptime>99.9%</StatusComponentUptime> * <StatusComponentStatus /> * </StatusComponentHeaderRight> * </StatusComponentHeader> * <StatusComponentBody> * <StatusBar data={uptimeData} /> * </StatusComponentBody> * </StatusComponent> * ``` * * @see StatusComponentHeader - For header layout * @see StatusComponentBody - For content area * @see StatusComponentIcon - For status indicator icon * @see StatusComponentStatus - For status label */export function StatusComponent({ variant, className, children, ...props}: StatusComponentProps) { return ( <div data-slot="status-component" data-variant={variant} className={cn("group/component space-y-2", className)} {...props} > {children} </div> );}StatusComponent.displayName = "StatusComponent";
// ============================================================================// Header Components// ============================================================================
/** * StatusComponentHeader - Header container for monitor status information * * Provides a flex container with space-between alignment for the monitor header, * typically containing title/description on the left and status/uptime on the right. * * @example * ```tsx * <StatusComponentHeader> * <StatusComponentHeaderLeft> * <StatusComponentIcon /> * <StatusComponentTitle>Database</StatusComponentTitle> * </StatusComponentHeaderLeft> * <StatusComponentHeaderRight> * <StatusComponentUptime>100%</StatusComponentUptime> * </StatusComponentHeaderRight> * </StatusComponentHeader> * ``` * * @see StatusComponentHeaderLeft - For left-aligned content * @see StatusComponentHeaderRight - For right-aligned content */export function StatusComponentHeader({ className, children, ...props}: React.ComponentProps<"div">) { return ( <div data-slot="status-component-header" className={cn("flex items-center justify-between gap-2", className)} {...props} > {children} </div> );}StatusComponentHeader.displayName = "StatusComponentHeader";
/** * StatusComponentHeaderLeft - Left-aligned header content container * * Provides a flex container with gap-2 spacing for left-aligned header elements, * typically containing the status icon, title, and optional description icon. * * @example * ```tsx * <StatusComponentHeaderLeft> * <StatusComponentIcon /> * <StatusComponentTitle>API Gateway</StatusComponentTitle> * <StatusComponentDescription> * Handles all incoming requests * </StatusComponentDescription> * </StatusComponentHeaderLeft> * ``` * * @see StatusComponentIcon - For status indicator * @see StatusComponentTitle - For service name * @see StatusComponentDescription - For info tooltip */export function StatusComponentHeaderLeft({ className, children, ...props}: React.ComponentProps<"div">) { return ( <div data-slot="status-component-header-left" className={cn("flex min-w-0 items-center gap-2 truncate", className)} {...props} > {children} </div> );}StatusComponentHeaderLeft.displayName = "StatusComponentHeaderLeft";
/** * StatusComponentHeaderRight - Right-aligned header content container * * Provides a flex container with gap-3 spacing for right-aligned header elements, * typically containing uptime percentage and status label. * * @example * ```tsx * <StatusComponentHeaderRight> * <StatusComponentUptime>99.95%</StatusComponentUptime> * <StatusComponentStatus /> * </StatusComponentHeaderRight> * ``` * * @see StatusComponentUptime - For uptime percentage display * @see StatusComponentStatus - For status label */export function StatusComponentHeaderRight({ className, children, ...props}: React.ComponentProps<"div">) { return ( <div data-slot="status-component-header-right" className={cn("flex items-center gap-3", className)} {...props} > {children} </div> );}StatusComponentHeaderRight.displayName = "StatusComponentHeaderRight";
// ============================================================================// Content Components// ============================================================================
/** * StatusComponentBody - Main content area for monitor visualizations * * Provides vertical spacing (space-y-2) for stacking content like status bars, * charts, or other status visualizations within the component. * * @example * ```tsx * <StatusComponentBody> * <StatusBar data={uptimeData} /> * <StatusComponentFooter data={uptimeData} /> * </StatusComponentBody> * ``` * * @see StatusBar - For uptime visualization bars * @see StatusComponentFooter - For footer with date range */export function StatusComponentBody({ className, children, ...props}: React.ComponentProps<"div">) { return ( <div data-slot="status-component-body" className={cn("space-y-2", className)} {...props} > {children} </div> );}StatusComponentBody.displayName = "StatusComponentBody";
// ============================================================================// Display Components// ============================================================================
/** * StatusComponentTitle - Monitor or service name display * * Displays the monitor/service name in monospace font with truncation for long names. * The text is medium weight and uses a base font size. * * @example * ```tsx * <StatusComponentTitle>Production API</StatusComponentTitle> * ``` * * @example * ```tsx * <StatusComponentTitle> * {monitor.name} * </StatusComponentTitle> * ``` */export function StatusComponentTitle({ children, className, ...props}: React.ComponentProps<"div">) { return ( <div data-slot="status-component-title" className={cn( "text-foreground truncate font-mono text-base leading-5 font-medium", className, )} {...props} > {children} </div> );}StatusComponentTitle.displayName = "StatusComponentTitle";
/** * StatusComponentDescription - Info icon with tooltip for additional details * * Displays an info icon that shows a tooltip on hover (or tap on touch devices) * with additional description text. Returns null if no children are provided, * allowing for conditional rendering. * * Touch device support is built-in, toggling the tooltip on tap instead of * requiring hover. * * @param children - The description text to show in the tooltip * * @example * ```tsx * <StatusComponentDescription> * This service handles user authentication and authorization * </StatusComponentDescription> * ``` * * @example * ```tsx * // Conditionally rendered - returns null if no description * <StatusComponentDescription> * {monitor.description} * </StatusComponentDescription> * ``` */export function StatusComponentDescription({ onClick, children, ...props}: React.ComponentProps<typeof TooltipTrigger>) { const isTouch = useMediaQuery("(hover: none)"); const [open, setOpen] = useState(false);
if (!children) return null;
return ( <TooltipProvider delayDuration={0}> <Tooltip open={open} onOpenChange={setOpen}> <TooltipTrigger onClick={(e) => { if (isTouch) setOpen((prev) => !prev); onClick?.(e); }} className="rounded-full" {...props} > <InfoIcon className="text-muted-foreground size-4" /> </TooltipTrigger> <TooltipContent> <p>{children}</p> </TooltipContent> </Tooltip> </TooltipProvider> );}StatusComponentDescription.displayName = "StatusComponentDescription";
/** * StatusComponentIcon - Status indicator icon for component context * * This component wraps the unified StatusIcon with variant="component", configuring * it to respond to the parent StatusComponent's data-variant attribute. * The displayed icon and color automatically change based on the status type: * - success: Green check icon * - degraded: Yellow warning triangle * - error: Red alert circle * - info: Blue wrench icon * * The icon is smaller (size-[12.5px]) than other variants, optimized for inline * display next to monitor titles. * * @example * ```tsx * <StatusComponent variant="degraded"> * <StatusComponentHeaderLeft> * <StatusComponentIcon /> * <StatusComponentTitle>CDN</StatusComponentTitle> * </StatusComponentHeaderLeft> * </StatusComponent> * ``` * * @see StatusComponent - For setting the variant context * @see StatusIcon from status-icon.tsx - For the underlying unified icon implementation */export function StatusComponentIcon({ className, ...props}: React.ComponentProps<"div">) { return ( <UnifiedStatusIcon variant="component" className={className} {...props} /> );}StatusComponentIcon.displayName = "StatusComponentIcon";
/** * StatusComponentFooter - Date range footer for status visualizations * * Displays a date range footer showing the time span of the displayed data, * with the start date on the left (formatted as relative time like "45 days ago") * and "today" on the right. Shows a skeleton loader when data is loading. * * If no data is available, displays a dash (-) on the left side. * * @param data - Array of status bar data points (uses first item's date for start) * @param isLoading - Whether the data is currently loading * * @example * ```tsx * <StatusComponentBody> * <StatusBar data={uptimeData} /> * <StatusComponentFooter data={uptimeData} isLoading={false} /> * </StatusComponentBody> * ``` * * @example * ```tsx * // With loading state * <StatusComponentFooter data={[]} isLoading={true} /> * ``` * * @see StatusBar - For the visualization that this footer describes */export function StatusComponentFooter({ data, isLoading,}: { data: StatusBarData[]; isLoading?: boolean;}) { const labels = useStatusBlocksLabels(); return ( <div data-slot="status-component-footer" className="text-muted-foreground flex flex-row items-center justify-between font-mono text-xs leading-none" > <div> {isLoading ? ( <Skeleton className="h-3 w-18" /> ) : data.length > 0 ? ( formatDistanceToNowStrict(subDays(new Date(), data.length), { unit: "day", addSuffix: true, }) ) : ( "-" )} </div> <div>{labels.today}</div> </div> );}StatusComponentFooter.displayName = "StatusComponentFooter";
/** * StatusComponentUptime - Uptime percentage display * * Displays the uptime percentage in monospace font with slightly muted foreground * color. Typically shows values like "99.9%" or "100%". * * @example * ```tsx * <StatusComponentUptime>99.95%</StatusComponentUptime> * ``` * * @example * ```tsx * <StatusComponentUptime> * {calculateUptime(data)}% * </StatusComponentUptime> * ``` * * @see StatusComponentUptimeSkeleton - For loading state */export function StatusComponentUptime({ className, children, ...props}: React.ComponentProps<"div">) { return ( <div data-slot="status-component-uptime" className={cn( "text-foreground/80 font-mono text-sm leading-none", className, )} {...props} > {children} </div> );}StatusComponentUptime.displayName = "StatusComponentUptime";
/** * StatusComponentUptimeSkeleton - Loading skeleton for uptime percentage * * Displays a skeleton loader matching the size of the uptime percentage display * (h-4 w-16), used while uptime data is being fetched. * * @example * ```tsx * <StatusComponentHeaderRight> * {isLoading ? ( * <StatusComponentUptimeSkeleton /> * ) : ( * <StatusComponentUptime>{uptime}%</StatusComponentUptime> * )} * </StatusComponentHeaderRight> * ``` * * @see StatusComponentUptime - For the actual uptime display */export function StatusComponentUptimeSkeleton({ className, ...props}: React.ComponentProps<typeof Skeleton>) { return <Skeleton className={cn("h-4 w-16", className)} {...props} />;}
/** * StatusComponentLatency - Latency chip shown next to the monitor title. * * Presentational only; wrap in the app's link primitive to navigate to the * monitor page. The caller supplies the label text (e.g. "92ms p75"). * * @see StatusComponentLatencySkeleton - For loading state */export function StatusComponentLatency({ className, children, ...props}: React.ComponentProps<"div">) { return ( <div data-slot="status-component-latency" className={cn( "text-muted-foreground flex items-center gap-1 rounded-md border px-1.5 py-0.5 font-mono text-xs leading-none", className, )} {...props} > {children} <ArrowUpRight className="size-3" /> </div> );}StatusComponentLatency.displayName = "StatusComponentLatency";
/** * StatusComponentLatencySkeleton - Loading skeleton sized to the latency chip. * * @see StatusComponentLatency - For the actual chip */export function StatusComponentLatencySkeleton({ className, ...props}: React.ComponentProps<typeof Skeleton>) { return ( <Skeleton className={cn("h-5 w-16 rounded-md", className)} {...props} /> );}StatusComponentLatencySkeleton.displayName = "StatusComponentLatencySkeleton";
/** * StatusComponentStatus - Automatic status label display * * Displays a status label that automatically shows the appropriate text and color * based on the parent StatusComponent's variant. The component uses CSS data * attribute selectors to show only the relevant status label: * - success: "Operational" (green) * - degraded: "Degraded" (yellow) * - error: "Outage" (red) * - info: "Maintenance" (blue) * * The labels are sourced from systemStatusLabels.short for consistent messaging * across the application. * * @example * ```tsx * <StatusComponent variant="success"> * <StatusComponentHeaderRight> * <StatusComponentStatus /> * // Displays "Operational" in green * </StatusComponentHeaderRight> * </StatusComponent> * ``` * * @example * ```tsx * <StatusComponent variant="degraded"> * <StatusComponentHeaderRight> * <StatusComponentStatus /> * // Displays "Degraded" in yellow * </StatusComponentHeaderRight> * </StatusComponent> * ``` * * @see StatusComponent - For setting the variant that controls the displayed status * @see systemStatusLabels - For the status label text definitions */export function StatusComponentStatus({ className, ...props}: React.ComponentProps<"div">) { const labels = useStatusBlocksLabels(); return ( <div data-slot="status-component-status" className={cn( "font-mono text-sm leading-none", "group-data-[variant=success]/component:text-success", "group-data-[variant=degraded]/component:text-warning", "group-data-[variant=error]/component:text-destructive", "group-data-[variant=info]/component:text-info", className, )} {...props} > <span className="hidden group-data-[variant=success]/component:block"> {labels.systemStatus.success.short} </span> <span className="hidden group-data-[variant=degraded]/component:block"> {labels.systemStatus.degraded.short} </span> <span className="hidden group-data-[variant=error]/component:block"> {labels.systemStatus.error.short} </span> <span className="hidden group-data-[variant=info]/component:block"> {labels.systemStatus.info.short} </span> </div> );}StatusComponentStatus.displayName = "StatusComponentStatus";