Cadenza
EN

记录里那些不是消息的行 —— 加入事件、日期分隔、「正在思考」

「Marcus 加入了对话」、一条日期分隔、「正在思考…」、某个工具正在跑、未读线。 凡是对话关于自己要说的话,而不是对话里的话,都是 Marker。

它与 Message 同级:在 MessageScroller 里,两者各自住在自己的 MessageScrollerItem 中。而且 marker 可以是锚点 —— 群聊里开启一轮的往往是 加入事件,不是某条消息。

封装层是纯转出:无状态、无 hook。

使用

import { Marker, MarkerContent, MarkerIcon } from '@gedatou/cadenza-ui'
<Marker variant="separator">
  <MarkerContent>今天</MarkerContent>
</Marker>

组成

Marker                 行本身:形状、变体
├── MarkerIcon         可选的图标,默认 aria-hidden
└── MarkerContent      文案

特性

  • 三种形状:素行、带下划线的行、带标签的分割线
  • 装饰性图标位,默认对辅助技术隐藏
  • render 让整行变成链接或按钮
  • 与 shimmer 工具类配合,做流式状态文字
  • 每个部件都能用 className 定制

变体

三种形状,差别全在文字周围的那条线:

变体长什么样
default一条素行 —— 状态、备注、动作
border行下方一条下划线,把它和接下来的内容分开
separator一条横线从两边伸出,标签居中夹在中间

三者共用同一套克制的排版(小字、muted)—— 一条 marker 永远不该抢消息的注意力。

状态

进行中的事 —— 正在思考、工具在跑、正在检索 —— 加 role="status",辅助技术才会在 这一行出现时播报它。没有它,屏幕阅读器只有在读者恰好摸过去时才会发现这条更新。

视觉上配一个 Spinner 放进 MarkerIcon,是同一个信号 的另一半。

Shimmer

给 MarkerContent 加 shimmer 工具类,让文字本身流动起来 —— 这是流式输出更诚实的 指示器:正在到达的东西,就是正在动的东西。

它和 Spinner 二选一,别同时用:文字已经是指示器了,旁边再放一个转圈是在说两遍 同样的话。

分隔

separator 变体用于带标签的分割线:日期分隔、章节分隔、未读线。两侧的横线是 CSS 伪元素,不进无障碍树 —— 被读出来的只有标签本身。

边框

border 变体保持 default 的左对齐(separator 会把标签推到居中),并在行下方 加一条下划线,把接下来的内容隔开。适合「引出下面这一段」的状态行。

图标

MarkerIcon 默认带 aria-hidden="true":图标重复的是文案已经说过的事,再念一遍 只会拖慢屏幕阅读器。想把图标叠在文字上方,给 Marker 加 flex-col。

链接与按钮

给 Marker 传 render,整行就变成一个 <a> 或 <button> —— 「查看那次改动」、 「加载更早的 12 条」。变体里已经写好了链接的下划线与 hover 色。

<Marker render={<a href="#" />}>
  <MarkerContent>查看运行顺序</MarkerContent>
</Marker>

无障碍

Marker 默认是纯展示的,正确的语义取决于你拿它做什么 —— 按意图选角色,不要指望 一个默认值通吃:

  • 进行中 → role="status"(礼貌播报,不打断)
  • 可点 → render={<button type="button" />} 或 render={<a href>},拿到焦点、 键盘激活和聚焦环
  • 纯装饰的分隔 → 什么都不加。separator 变体的横线是 CSS 伪元素,本来就不进 无障碍树
  • 有意义的分隔 → 日期分隔这类,考虑 role="separator" 加 aria-label
  • border 变体 → 那条下划线纯属视觉,它把下方内容隔开是给眼睛看的;辅助技术 要感知到这层分组,得靠外层真正的分区语义(role="group" + 名字,或一个标题), 而不是这条线

图标始终是装饰性的,语义在 MarkerContent 的文案里。

什么时候用 Marker

要显示一条消息,用 Message + Bubble。 要显示的是对话本身发生的事,用 Marker。

要一条纯视觉的分割线、与对话无关,用 Separator(本库尚未提升);Marker 的 separator 变体带的是文案,横线只是陪衬。

状态与 className

属性出现在值
data-variantMarker"default" | "border" | "separator"
data-slot是什么
marker行
marker-icon图标位(aria-hidden)
marker-content文案

Marker 带 group/marker,MarkerContent 靠 group-data-[variant=separator]/marker 知道自己该居中。

根的 className 走 cva,cva 会丢掉函数而不是解析它,所以这里的类型诚实地 是 string —— 要按状态改样式请用上面的 data-*。

导出的类型

import type {
  MarkerContentProps,
  MarkerIconProps,
  MarkerProps,
  MarkerVariant,
} from '@gedatou/cadenza-ui'

markerVariants 也一并导出,用于在别处复用同一套排版。

Props

顺序规则:必填 → 非受控默认值 → 受控值 → 回调 → 行为开关 → 外观 → className。

Marker

行本身。

Prop类型默认值说明
variant'default' | 'border' | 'separator''default'行的形状
renderReactElement | (props, state) => ReactElement—换掉渲染的元素;传 <a> 或 <button> 让整行可点
classNamestring—走 cva,是字符串
其余ComponentProps<'div'>(含 ref)—透传

MarkerIcon

图标位。渲染成 <span>,默认 aria-hidden="true"。

Prop类型默认值说明
classNamestring—落在纯 <span> 上
其余ComponentProps<'span'>(含 ref)—透传

MarkerContent

文案。separator 变体下它会居中并停止伸展,把空间让给两侧的横线。

Prop类型默认值说明
classNamestring—落在纯 <span> 上;shimmer 加在这里
其余ComponentProps<'span'>(含 ref)—透传