OpenStatus 状态页 Blocks 组件架构:基于组合模式的状态展示组件库实战指南
【免费下载链接】openstatus🫖 Status page with uptime monitoring & API monitoring as code 🫖项目地址: https://gitcode.com/GitHub_Trending/op/openstatus
OpenStatus 的@openstatus/ui包内置了一套名为Status Blocks的高阶组件集合,用于构建状态页(Status Page)、事件报告(Incident Report)与可用性仪表盘(Uptime Dashboard)的完整 UI。本指南以 packages/ui/src/components/blocks/README.md 为核心,结合源码深入讲解其组合式架构、全部组件分类、5 大组合范式、i18n 本地化机制、主题定制与无障碍实现,读完即可在你的状态页应用中自由装配这套组件。
设计哲学:组合优先(Composition-First)
Blocks 组件与常见的"大而全"监控组件不同,它们建立在组合模式之上:与其使用一个带几十个 props 的巨型<Monitor />组件,不如将多个职责单一的小组件组合起来,达成任意布局与行为。
以"展示一个 API 监控项"为例,传统做法是:
<Monitor name="API" status="success" uptime="99.9%" showIcon={true} showStatus={true} />而 Blocks 的做法是显式组合:
<StatusComponent variant="success"> <StatusComponentHeader> <StatusComponentHeaderLeft> <StatusComponentIcon /> <StatusComponentTitle>API</StatusComponentTitle> </StatusComponentHeaderLeft> <StatusComponentHeaderRight> <StatusComponentUptime>99.9%</StatusComponentUptime> <StatusComponentStatus /> </StatusComponentHeaderRight> </StatusComponentHeader> </StatusComponent>从 status-component.tsx 的源码可以看到,StatusComponent本身只做两件事:通过data-slot="status-component"与data-variant={variant}建立状态上下文,并通过group/component类成为一个 CSS group。它不直接渲染图标、标题或状态文案——这些都是子组件通过 CSS 选择器感知父级上下文后自行呈现的。
五大设计原则
- 关注点分离:每个组件只有单一、明确的职责;
- 数据属性驱动样式:组件通过
data-*属性传递状态,样式完全由 CSS 选择器响应; - CSS Group 模式:父组件通过
group类建立上下文,子组件据此自我定位; - Headless 钩子:复杂交互逻辑与表现层解耦(典型代表是
useStatusBar); - 组合优于配置:通过组合组件而非堆叠 props 达成目标。
组件分层架构
从 README 的分层图可以清晰看到各组件的定位:
┌──────────────────────────────────────────────────────────┐ │ Layout Components (Status, StatusComponent, StatusEvent) │ │ └─ Establish context via><StatusComponent variant="degraded"> {/* 子组件自动按 degraded 状态着色 */} </StatusComponent>.group-data-[variant=degraded]/component:text-warning这一机制在StatusComponentStatus中有完整体现(status-component.tsx):组件一次性渲染全部 4 种状态的文案<span>,再用hidden group-data-[variant=success]/component:block等类做到"仅显示当前状态对应的标签",从而避免任何 JavaScript 条件渲染。
StatusIcon(status-icon.tsx)同样遵循此模式,且通过variant属性区分三种选择器上下文:
default:使用group-data-[variant=...],用于Status组件;banner:使用group-data-[status=...]/status-banner,用于StatusBanner;component:使用group-data-[variant=...]/component,用于StatusComponent。
图标映射为:success →CheckIcon,degraded →TriangleAlertIcon,error →AlertCircleIcon,info →WrenchIcon,empty → 无图标(仅弱化背景)。
组件全览:九大分类
1. 布局组件(Layout Components)
Status 系列(status-layout.tsx):
- Status:状态页根容器,通过
data-variant建立整页状态上下文,同时充当group与peer; - StatusHeader:品牌与标题区,自带
@container/status-header容器查询上下文(响应其自身宽度而非视口); - StatusTitle / StatusDescription:页标题与副标题(使用语义化 div 而非 h1,方便接入方自行决定标题层级);
- StatusContent:主内容区,纵向 flex 布局(
gap-3); - StatusBrand:品牌 Logo(默认 32×32);
- StatusIcon:状态指示图标的
default变体包装。
Monitor 系列(status-component.tsx):
- StatusComponent:单个监控项/服务容器(
variant必填,取值Exclude<StatusType, "empty">); - StatusComponentHeader / HeaderLeft / HeaderRight:监控头部的左中右布局容器(左侧
min-w-0 truncate防溢出,右侧gap-3对齐); - StatusComponentBody:监控内容区(
space-y-2,用于堆叠 StatusBar 与 Footer)。
2. 状态展示组件
- StatusComponentIcon:监控项状态指示图标(比默认更小,
size-[12.5px],适合内联展示); - StatusComponentTitle:服务名(等宽字体、可截断);
- StatusComponentDescription:信息图标 + Tooltip(无内容时返回 null,支持触摸设备点按切换);
- StatusComponentUptime:可用性百分比(等宽字体);
- StatusComponentStatus:自动状态标签(纯 CSS 切换,见上文);
- StatusComponentFooter:日期范围页脚,左侧显示相对时间(如 "45 days ago",基于
formatDistanceToNowStrict),右侧显示today标签;另有StatusComponentUptimeSkeleton、StatusComponentLatency(延迟徽章)及其骨架屏用于加载态。
3. 状态横幅组件(StatusBanner)
用于系统级状态的醒目横幅(status-banner.tsx):StatusBanner完整横幅、StatusBannerContainer可定制容器、StatusBannerMessage自动状态文案、StatusBannerTitle彩色标题条、StatusBannerContent内容区、StatusBannerIcon图标,以及StatusBannerTabs/TabsList/TabsTrigger/TabsContent多区块选项卡。
4. 状态条组件(StatusBar)
交互式可用性时间轴,是整套组件中最复杂的部分(status-bar.tsx):
- StatusBar:主时间轴,每天一个竖条,条内按状态分段着色;
- useStatusBar:Headless 钩子,完全分离交互状态与表现;
- StatusBarEvent:悬停卡内的事件徽章;
- StatusBarSkeleton:加载骨架屏(高 50px)。
StatusBar的数据结构StatusBarData定义于 status.types.ts,每天一个数据项:day日期字符串、bar状态分段数组(height为百分比、须合计 100%)、card悬停卡状态明细、events当天事件(可带isAggregated聚合标记、shortlink外部链接、以及覆盖类型默认颜色的status字段)。
useStatusBar 的交互模式
从源码注释与实现看,该钩子实现了三种交互模式:
- hover:桌面端悬停预览卡片,移出后自动隐藏(带 100ms 延迟清除);
- pin:点击固定卡片,再次点击或按 Esc 关闭,点击组件外部也会自动关闭;
- focus:键盘聚焦时显示卡片,失焦时隐藏。
键盘导航完整支持:←/→在同一天时间内移动、↑/↓跨监控时间轴移动(通过closest('[data-slot="status-component"]')查找相邻监控)、Enter/Space固定/取消固定、Esc关闭并移除焦点。触摸设备通过useMediaQuery("(hover: none)")检测并禁用 hover 态,改为点按切换。
悬停卡StatusBarCard由三部分组成:日期头(formatDateShort)、状态明细行(StatusBarContent,有impact字段时优先显示componentImpact文案而非通用requestStatus)、以及事件列表。事件徽章StatusBarEvent依据事件类型决定圆点颜色(incident → 红、report → 黄、maintenance → 蓝),持续时间文案经formatDuration处理:无结束时间显示ongoing、聚合事件显示across {duration}、零时长隐藏。
5. 事件组件(StatusEvents)
用于事件报告与维护通知(status-events.tsx):
容器组件:StatusEventGroup(带role="feed"的 feed 容器)、StatusEvent(单事件容器,相对定位以配合绝对定位的日期栏)、StatusEventContent(可悬停内容区,hoverable可关闭悬停效果)。
内容组件:StatusEventTitle、StatusEventTitleCheck(绿色圆点"已解决"徽标 + Tooltip)、StatusEventAffected与StatusEventAffectedBadge(受影响服务徽章)。
日期组件:StatusEventDate(格式化日期 + 相对时间徽章,未来日期高亮 info 色)、StatusEventAside(桌面端左侧 sticky 日期栏,滚动长事件时保持可见)。
时间轴组件:StatusEventTimelineReport/StatusEventTimelineReportUpdate/StatusEventTimelineMaintenance、StatusEventTimelineTitle、StatusEventTimelineMessage、StatusEventTimelineDot(彩色状态点)、StatusEventTimelineSeparator(连接线),以及StatusEventTimelineImpact——按worstStatusReportImpact(status.utils.ts,按statusReportImpacts数组索引比较)计算最严重影响的悬停卡标签。
6. 空状态组件
StatusBlankEvents(无事件空态)、StatusBlankMonitors(无监控空态)、StatusBlankContainer通用容器、StatusBlankTitle、StatusBlankDescription,以及StatusBlankAction(空态 CTA 的样式化外层)。
7. 工具组件
- StatusTimestamp:时区感知时间戳(悬停显示详情,status-timestamp.tsx);
- StatusFeed:事件与维护的统一 feed(status-feed.tsx);
- StatusComponentGroup:可折叠的监控分组(status-component-group.tsx);
- StatusCalendar:状态日历块(status-calendar.tsx,其标题由
labels.calendarTitle提供)。
8. i18n / 本地化
- StatusBlocksI18nProvider:React Context,向所有 blocks 提供翻译文案与 locale 感知的日期格式化器;
- useStatusBlocksLabels:blocks 读取文案的钩子,无 Provider 时回退到
defaultStatusBlocksLabels; - defaultStatusBlocksLabels:英文(
en-US)默认文案,导出自 status.utils.ts。
9. 页面 Chrome(头部 / 页脚 / 切换器)
纯表现层的"骨架块",用于把上述主体组件组装成完整状态页:Shell(StatusPageShell外层、StatusPageMain内层——后者带 embed 感知的 Tailwind 类,仅在祖先设置data-embed=true的group/embed元素时激活);Header(StatusPageHeader、StatusPageHeaderContent、StatusPageHeaderBrand、StatusPageHeaderBrandButton、StatusPageHeaderBrandFallback、StatusPageHeaderNav、StatusPageHeaderNavItem、StatusPageHeaderActions);Footer(StatusPageFooter、StatusPageFooterContent、StatusPagePoweredBy、StatusPageFooterActions);Get-in-Touch(StatusPageGetInTouchIcon、StatusPageGetInTouchButton);订阅渠道(StatusUpdates系列 Popover,含StatusUpdatesCopyInput、StatusUpdatesRss、StatusUpdatesJson、StatusUpdatesSlack、StatusUpdatesSsh等各渠道订阅块);切换器(StatusThemeSwitcher、StatusThemeSwitcherSkeleton、StatusLocaleSwitcher、StatusLocaleSwitcherSkeleton——均为无状态受控组件,由调用方持有value+onValueChange)。
所有这些 Chrome 块都是路由、主题、locale 无关的——调用方自行接入自己的next/link、next-themes、next-intl。带链接需求的按钮(如品牌按钮、导航项)使用asChild将 Radix Slot 传递给调用方的链接组件。
五大组合范式(Composition Patterns)
README 给出了 5 个可直接复制的实战范式,这里逐一展开并补充数据要点。
范式 1:基础监控展示
<StatusComponent variant="success"> <StatusComponentHeader> <StatusComponentHeaderLeft> <StatusComponentIcon /> <StatusComponentTitle>Production API</StatusComponentTitle> <StatusComponentDescription> Handles all production traffic </StatusComponentDescription> </StatusComponentHeaderLeft> <StatusComponentHeaderRight> <StatusComponentUptime>99.95%</StatusComponentUptime> <StatusComponentStatus /> </StatusComponentHeaderRight> </StatusComponentHeader> <StatusComponentBody> <StatusBar data={uptimeData} /> <StatusComponentFooter data={uptimeData} /> </StatusComponentBody> </StatusComponent>其中uptimeData即StatusBarData[],如:
const uptimeData = [ { day: "2024-01-15", bar: [{ status: "success", height: 100 }], card: [{ status: "success", value: "100%" }], events: [] }, { day: "2024-01-16", bar: [ { status: "success", height: 80 }, { status: "error", height: 20 } ], card: [ { status: "success", value: "80%" }, { status: "error", value: "20%" } ], events: [ { id: "inc-1", type: "incident", name: "API Downtime", from: new Date("2024-01-16T10:00:00Z"), to: new Date("2024-01-16T10:30:00Z") } ] } ];范式 2:带选项卡的状态横幅
<StatusBannerTabs status="degraded" defaultValue="impact"> <StatusBannerTabsList> <StatusBannerTabsTrigger value="impact" status="degraded"> Impact </StatusBannerTabsTrigger> <StatusBannerTabsTrigger value="updates" status="degraded"> Updates </StatusBannerTabsTrigger> </StatusBannerTabsList> <StatusBannerTabsContent value="impact"> <StatusBannerTitle>Degraded Performance</StatusBannerTitle> <StatusBannerContent> <p>We are experiencing elevated latency across all regions.</p> <StatusEventAffected> <StatusEventAffectedBadge>API</StatusEventAffectedBadge> <StatusEventAffectedBadge>Database</StatusEventAffectedBadge> </StatusEventAffected> </StatusBannerContent> </StatusBannerTabsContent> <StatusBannerTabsContent value="updates"> <StatusBannerContent> <StatusEventTimelineReport updates={incidentUpdates} /> </StatusBannerContent> </StatusBannerTabsContent> </StatusBannerTabs>范式 3:事件时间轴
事件更新按"最新在前"排序展示,updates元素类型为StatusReportUpdate(date/message/status,可选impactChanges记录本次更新中各组件的影响变化):
<StatusEvent> <StatusEventAside> <StatusEventDate date={incidentDate} /> </StatusEventAside> <StatusEventContent> <div className="flex items-center gap-2"> <StatusEventTitle>API Gateway Outage</StatusEventTitle> <StatusEventTitleCheck /> </div> <StatusEventAffected> <StatusEventAffectedBadge>API Gateway</StatusEventAffectedBadge> <StatusEventAffectedBadge>Authentication</StatusEventAffectedBadge> </StatusEventAffected> <StatusEventTimelineReport updates={[ { status: "resolved", message: "All services have been restored", date: new Date("2024-01-15T12:00:00Z") }, { status: "monitoring", message: "Fix deployed, monitoring recovery", date: new Date("2024-01-15T11:45:00Z") }, { status: "identified", message: "Root cause identified in load balancer", date: new Date("2024-01-15T11:15:00Z") }, { status: "investigating", message: "Investigating API timeouts", date: new Date("2024-01-15T11:00:00Z") } ]} /> </StatusEventContent> </StatusEvent>范式 4:统一 Feed
StatusFeed同时接收statusReports与maintenances,自动合并为按时间排序的统一流:
<StatusFeed statusReports={[ { id: 1, title: "API Outage", affected: ["API", "Database"], updates: [ { status: "resolved", message: "Issue resolved", date: new Date() } ] } ]} maintenances={[ { id: 2, title: "Database Upgrade", message: "Upgrading to PostgreSQL 15", affected: ["Database"], from: new Date("2024-01-20T02:00:00Z"), to: new Date("2024-01-20T04:00:00Z") } ]} />范式 5:分组监控
StatusComponentGroup支持折叠,defaultOpen控制默认展开状态:
<StatusContent> <StatusComponentGroup title="Core Services" status="success" defaultOpen={true} > <StatusComponent variant="success"> <StatusComponentHeader> <StatusComponentHeaderLeft> <StatusComponentIcon /> <StatusComponentTitle>API Server</StatusComponentTitle> </StatusComponentHeaderLeft> </StatusComponentHeader> </StatusComponent> <StatusComponent variant="success"> <StatusComponentHeader> <StatusComponentHeaderLeft> <StatusComponentIcon /> <StatusComponentTitle>Database</StatusComponentTitle> </StatusComponentHeaderLeft> </StatusComponentHeader> </StatusComponent> </StatusComponentGroup> <StatusComponentGroup title="Third-Party Services" status="degraded" defaultOpen={false} > <StatusComponent variant="degraded"> <StatusComponentHeader> <StatusComponentHeaderLeft> <StatusComponentIcon /> <StatusComponentTitle>Payment Gateway</StatusComponentTitle> </StatusComponentHeaderLeft> </StatusComponentHeader> </StatusComponent> </StatusComponentGroup> </StatusContent>国际化(i18n)机制深入
Blocks 的所有面向用户的字符串、ARIA 标签与日期格式化器,都通过useStatusBlocksLabels()钩子从同一个StatusBlocksLabels对象获取。Blocks从不直接导入next-intl、react-intl等任何 i18n 库——这是它们可以被 shadcn registry 发布、同时仍能被消费方应用本地化的关键契约。
默认行为(无 Provider)
未挂载 Provider 时,blocks 开箱即用地渲染英文(en-US)文案:
import { StatusBanner } from "@openstatus/ui/components/blocks/status-banner"; <StatusBanner status="success" /> // Renders: "All Systems Operational" with en-US date formattinguseStatusBlocksLabels()的实现(status-i18n.tsx)非常简洁:useContext(StatusBlocksLabelsContext) ?? defaultStatusBlocksLabels——Context 为空即回退默认值。
在应用中本地化
在组件树根部挂载一次<StatusBlocksI18nProvider>,传入一个StatusBlocksLabels对象:
"use client"; import { useMemo } from "react"; import { useTranslations, useLocale } from "next-intl"; // or any i18n library import { StatusBlocksI18nProvider, type StatusBlocksLabels, } from "@openstatus/ui/components/blocks/status-i18n"; export function MyStatusBlocksProvider({ children }: { children: React.ReactNode }) { const t = useTranslations(); const locale = useLocale(); const labels = useMemo<StatusBlocksLabels>(() => { const dateFmt = new Intl.DateTimeFormat(locale, { dateStyle: "long" }); const dateShortFmt = new Intl.DateTimeFormat(locale, { dateStyle: "medium" }); const dateTimeFmt = new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short", }); return { systemStatus: { success: { long: t("All Systems Operational"), short: t("Operational") }, degraded: { long: t("Degraded Performance"), short: t("Degraded") }, error: { long: t("Partial Outage"), short: t("Outage") }, info: { long: t("Maintenance"), short: t("Maintenance") }, empty: { long: t("No Data"), short: t("No Data") }, }, incidentStatus: { resolved: t("Resolved"), monitoring: t("Monitoring"), identified: t("Identified"), investigating: t("Investigating"), }, requestStatus: { success: t("Normal"), degraded: t("Degraded"), error: t("Error"), info: t("Maintenance"), empty: t("No Data"), }, today: t("today"), ongoing: t("ongoing"), reportResolved: t("Report resolved"), noRecentNotifications: t("No recent notifications"), // ...remaining labels (see StatusBlocksLabels type for the full list) ariaStatusTracker: t("Status tracker"), ariaDayStatus: (n) => t("Day {n} status", { n }), clickAgainToUnpin: t("Click again to unpin"), durationIn: (s) => t("(in {duration})", { duration: s }), durationEarlier: (s) => t("({timeFromLast} earlier)", { timeFromLast: s }), durationFor: (s) => t("(for {duration})", { duration: s }), durationAcross: (s) => t("across {duration}", { duration: s }), formatDate: (d) => dateFmt.format(d), formatDateShort: (d) => dateShortFmt.format(d), formatDateTime: (d) => dateTimeFmt.format(d), formatDateRange: (from, to) => from && to ? `${dateFmt.format(from)} – ${dateFmt.format(to)}` : "", }; }, [t, locale]); return ( <StatusBlocksI18nProvider value={labels}>{children}</StatusBlocksI18nProvider> ); }在 locale 布局(或应用根部)挂载到所有 blocks 之上:
<NextIntlClientProvider locale={locale} messages={messages}> <MyStatusBlocksProvider>{children}</MyStatusBlocksProvider> </NextIntlClientProvider>StatusBlocksLabels的完整类型定义见 status-i18n.tsx,它涵盖:四个状态文案组(systemStatus、incidentStatus、requestStatus、componentImpact)、空态文案(noReports、noPublicMonitors等)、订阅文案(subscribe、各渠道描述、linkCopiedToClipboard)、主题文案(themeNames、ariaToggleTheme)、以及 5 个日期格式化函数(formatDate/formatDateShort/formatDateTime/formatDateRange/formatDateRangeParts)。
为什么用 Context 而不是 props?
像StatusComponentStatus和StatusBannerMessage这类组件使用纯 CSS 条件渲染(data-[variant=...]:block)来切换文案——它们需要在渲染时拿到全部状态文案,而不仅是"当前激活的那一个"。Context 让它们无需逐层 prop drilling 就能获得全部标签。
为什么 blocks 内部不导入 next-intl?
@openstatus/ui通过 shadcn registry 分发。若直接依赖next-intl,会强制所有消费者(无论是否使用 Next.js)都安装它。Provider 模式让消费方应用用自己的 i18n 方案把翻译桥接进 blocks。
仅自定义日期格式
如果只需要 locale 感知的日期(英文文案即可),只覆盖 formatter 字段并展开其余默认值:
import { defaultStatusBlocksLabels } from "@openstatus/ui/components/blocks/status.utils"; const labels: StatusBlocksLabels = { ...defaultStatusBlocksLabels, formatDate: (d) => new Intl.DateTimeFormat(locale, { dateStyle: "long" }).format(d), formatDateShort: (d) => new Intl.DateTimeFormat(locale, { dateStyle: "medium" }).format(d), formatDateTime: (d) => new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short" }).format(d), };默认标签与日期工具的源码细节
status.utils.ts 是整套标签体系的单一事实来源,值得关注几个实现细节:
- 所有默认标签按用途分组导出:
systemStatusLabels(含 deprecated 别名messages)、requestStatusLabels(别名requests)、incidentStatusLabels(别名status)、componentImpactLabels、statusColors(别名colors,把五种状态映射到 CSS 变量:success →var(--success),degraded →var(--warning),error →var(--destructive),info →var(--info),empty →var(--muted)); - 日期格式化工具
formatDate/formatDateShort/formatDateTime/formatTime强制timeZone: "UTC"(注释说明(UTC)后缀依赖它,且放在展开项末尾防止调用方覆盖); formatDateRange未使用Intl.DateTimeFormat.formatRange(),而是自定义实现,以精确控制 "Since…"、"Until…"、"All time" 等边界情况的输出(同文件 JSDoc 说明了理由);formatDateRangeParts返回独立的{ from, to }字符串,让调用方可以分别包裹悬停卡,而无需重新解析拼接结果(StatusBlocksLabels类型注释要求实现方对同日区间做与formatDateRange一致的折叠);defaultStatusBlocksLabels中的所有日期函数都附加了withUTC后缀,向用户明确时间戳的 UTC 时区属性。
扩展插槽(Extension Slots)
Blocks 暴露 slot 属性,让"应用专属的关注点"(如 Next.js<Link>、Markdown 渲染)留在 registry 之外,同时允许消费方注入:
| Block | Slot | 用途 |
|---|---|---|
StatusEventTimelineReport | renderMessage?: (msg: string) => ReactNode | 把报告消息体接入 Markdown 渲染器(如<ProcessMessage>) |
StatusEventTimelineMaintenance | renderMessage?: (msg: string) => ReactNode | 同上,用于维护消息体 |
StatusEventTimelineTitle | asChild?: boolean | 用自定义元素(如<Link>)包裹标题——收窄点击目标且不破坏嵌套交互 HTML |
StatusBar | renderEvent?: (event, index) => ReactNode | 用 Next<Link>包裹事件徽章(路由到事件详情页) |
StatusBar | renderBar?/renderCard? | 自定义条分段与悬停卡内容 |
StatusFeed | renderEvent?: (event, content) => ReactNode | 包裹每条事件行(如加<Link>) |
StatusFeed | renderReportMessage?/renderMaintenanceMessage? | 转发给上述时间轴块 |
StatusFeed | footer?: ReactNode | 在 feed 下方追加内容(如"查看事件历史"链接) |
StatusFeed | emptyAction?: ReactNode | 在空态内部追加内容 |
StatusBlankEvents/StatusBlankMonitors | action?: ReactNode | 空态内的 CTA,包裹在<StatusBlankAction>样式外层中 |
省略插槽时保持默认行为——registry 预览与未配置的消费者无需任何接线即可正确渲染。
主题与样式
CSS 变量
组件通过 CSS 变量定义状态颜色:
--success: /* Green */ --warning: /* Yellow */ --destructive: /* Red */ --info: /* Blue */ --muted: /* Gray */暗色模式
所有组件通过 CSS 变量天然支持暗色模式——父元素上的dark类会自动切换暗色配色,无需任何组件级配置。
自定义样式
任何组件都可用className覆盖样式:
<StatusComponent variant="success" className="custom-styles"> {/* Component content */} </StatusComponent>此外,StatusBar还接受container属性,可指定 Radix Portal 的挂载容器(默认document.body)——当状态条渲染在覆盖--radius等 CSS 变量的作用域子树(如状态页预览)中时,传入该容器 ref 可让 portal 卡片继承相同的主题上下文。
无障碍(Accessibility)实现
键盘导航
- StatusBar:完整方向键导航——
←/→在同一天时间内移动,↑/↓在监控时间轴之间移动,Enter/Space固定/取消固定,Esc关闭卡片并移除焦点; - StatusComponentGroup:
Space/Enter切换展开,标准可折叠组件键盘支持; - StatusBannerTabs:Tab 键导航,方向键切换选项卡。
ARIA 支持
StatusBar根元素带role="toolbar"与aria-label={labels.ariaStatusTracker},每个条带role="button"、aria-label(如 "Day 3 status")、aria-pressed(固定态)与aria-expanded;StatusEventGroup带role="feed"事件流语义;- Tooltip 触发器带
aria-label供屏幕阅读器识别。
屏幕阅读器
StatusTimestamp以无障碍格式附带时区信息;StatusIcon的各变体通过可访问标签区分;- 空状态包含描述性文本提供上下文(如
noReportsDescription、noPublicMonitorsDescription)。
结语
Status Blocks 组件库展示了"组合优先、Headless 分离、data-attribute 驱动样式、Context 桥接 i18n"这一套现代 UI 组件设计范式在状态页场景下的完整落地。全部组件实现位于 packages/ui/src/components/blocks,其中:
- status-layout.tsx:页面布局组件;
- status-component.tsx:监控展示组件;
- status-banner.tsx:状态横幅组件;
- status-bar.tsx:可用性时间轴组件;
- status-events.tsx:事件与事件报告组件;
- status-feed.tsx:统一 Feed 组件;
- status-timestamp.tsx:时间戳组件;
- status-blank.tsx:空状态组件;
- status-component-group.tsx:分组组件;
- status-icon.tsx:图标组件;
- status-i18n.tsx:i18n Provider、Hook 与
StatusBlocksLabels类型; - status.utils.ts:工具函数与
defaultStatusBlocksLabels; - status.types.ts:全部共享类型定义;
- 以及
status-page-shell.tsx、status-page-header.tsx、status-page-footer.tsx、status-page-get-in-touch.tsx、status-updates.tsx、status-theme-switcher.tsx、status-locale-switcher.tsx等页面 Chrome 组件。
每个组件的详细 API 以 JSDoc 注释形式内嵌于对应文件。这套组件遵循 headless UI 模式,将状态管理与表现分离,在保持类型安全与无障碍的同时提供了最大灵活性。
【免费下载链接】openstatus🫖 Status page with uptime monitoring & API monitoring as code 🫖项目地址: https://gitcode.com/GitHub_Trending/op/openstatus
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考