Cell · List 列表行
列表中的信息行,可组合文字、图标、操作和附加内容;使用 List 组织分组或紧凑列表。
代码演示
设置
通知
由行尾开关控制
版本
v1.0
客户
disabled action · handler 会被移除
操作结果:尚未操作视觉规范
| 元素 | 规则 |
|---|---|
| 整组容器(grouped) | c.background 浅灰底(视觉分组),padding: 8(space[3]),borderRadius: 12(radius.xl),gap: 8 |
| 单行 | c.surface 白底,borderRadius: 10(radius.lg),min-height: 56,padding: 14(space[6],水平 + 垂直同值) |
| 行内布局 | 左 icon (grouped 22 / flush 28 盒) + 中 (title 15/500 + desc 13/subtle) + 右 extra (13/subtle) + arrow (grouped 20 / flush 18) |
| 绝不(grouped) | border-bottom、hairline 分隔(flush 模式才用 inset hairline) |
用法
import { Cell, List, Switch } from '@unif/react-native-design';
<List>
<Cell
title="主题"
extra={{ kind: 'text', value: '跟随系统' }}
arrow
leading="settings"
onPress={openTheme}
/>
<Cell
title="拜访提醒"
desc="按计划自动推送"
extra={{
kind: 'control',
node: (
<Switch
value={notify}
onChange={setNotify}
accessibilityLabel="拜访提醒"
/>
),
}}
/>
<Cell title="版本" extra={{ kind: 'text', value: 12n }} />
</List>;
{
/* 平铺式(无浅灰背景容器,用于嵌套在其它白卡内)*/
}
<List flush>
<Cell
title="客户名"
desc="地址"
extra={{ kind: 'text', value: '¥1,200' }}
arrow
onPress={openCustomer}
/>
</List>;
API
公开内容类型:
type CellTextValue = string | number | bigint;
type CellLeading = IconName | { kind: 'display'; node: ReactElement };
type CellExtra =
| { kind: 'text'; value: CellTextValue }
| {
kind: 'display';
node: ReactElement;
accessibilityText?: string;
}
| { kind: 'control'; node: ReactElement };
type ActionableCellExtra = Exclude<CellExtra, { kind: 'control' }>;
type StaticCellExtra =
| Extract<CellExtra, { kind: 'text' }>
| {
kind: 'display';
node: ReactElement;
accessibilityText?: never;
};
公开 CellExtra 保持完整三分联合;CellProps 再按分支收紧。accessibilityText 只属于 actionable display,static display 精确禁止该字段。
<Cell>
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title | CellTextValue | — | 主标题(必填);始终由库内 <Text> + stringifyCellText 安全渲染 |
titleLines | number | 1 | 主标题的 numberOfLines |
desc | CellTextValue | — | 副标题 / 描述;始终由库内 <Text> 安全渲染,最多 2 行 |
extra | CellExtra | — | 三种公开配置;actionable 可用 text/display,static 仅 text/装饰 display,control 仅 control |
arrow | boolean | false | 仅 actionable 可用;显示 chevron-right,danger 时仍不渲染 |
leading | CellLeading | — | 图标名,或 { kind: 'display', node } 纯展示节点 |
onPress | () => void | — | actionable 分支必填;control/static 禁止 |
disabled | boolean | false | 仅 actionable 可用;禁用时移除 handler、半透明并上报 disabled |
danger | boolean | false | 危险态:icon 盒子 + 标题用 error 红,且不渲染 arrow(常用于退出登录 / 删除等) |
style | StyleProp<ViewStyle> | — | 额外样式覆盖(合并到 cell 容器的 style 数组) |
testID | string | — | E2E / 测试定位 |
accessibilityLabel | string | 自动组合 | 仅 actionable 可用;覆盖自动名称 |
accessibilityHint | string | — | 仅 actionable 可用;说明操作结果 |
CellProps 是三个互斥分支:
| 分支 | 必要条件 | 可用 extra | 外层语义 |
|---|---|---|---|
| actionable | onPress 必填 | text / display | RNGH Pressable + button |
| control | extra.kind === 'control' | control | 本地 View;control 自己承担交互与名称 |
| static | 无 onPress | text / 无 accessibilityText 的 display | 本地 View;禁止 arrow / disabled / action a11y props |
<List>
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
children | ReactNode | — | 一组 <Cell> |
flush | boolean | false | 透明背景(嵌套用);省略则白卡 + 8px gap + 浅灰底容器 |
divider | 'full' | 'none' | 'full' | flush 模式下行间分隔线:'full' 铺满全宽 hairline / 'none' 不画 |
style | StyleProp<ViewStyle> | — | 额外样式覆盖 |
testID | string | — | E2E / 测试定位 |
无障碍(a11y)
来源:src/components/ui/Cell/Cell.tsx、content.ts、types.ts。
- actionable 是唯一外层可访问操作:RNGH
Pressable固定为button。默认名称依次组合 trim 后非空的title、desc与 extra 文本,使用中文顿隔,;actionable display 的accessibilityText只有非空白时才贡献。trim 后非空的显式accessibilityLabel优先,空白覆盖回退自动名称;最终名称仍为空时整行失败关闭为本地 View,不保留外层 handler/button/arrow,并在 effect 诊断。 disabled=true时 actionable 同时设置 disabled state 和onPress={undefined},不会留下可调用 handler。- control/static 都渲染本地
View,不创建外层 action 或用于合并名称的 a11y node。control 的 node 必须自己提供 role、state、handler 与 accessible name。 - 所有 leading、自定义
displayextra 与 chevron 都由库内本地View包裹,并用共享隐藏属性移出 a11y tree;只有 actionable display 的accessibilityText会进入外层名称,不形成第二焦点。 title、desc与textextra 始终由库内Text渲染,0/bigint不会成为View的裸 child。static 需要语义内容时必须使用kind: 'text',由可见 Text 自然朗读;static display 始终是装饰内容,类型精确禁止accessibilityText。
// 默认读作“设备,在线”;display node 本身不形成第二焦点
<Cell
title="设备"
extra={{
kind: 'display',
node: <StatusDot status="active" />,
accessibilityText: '在线',
}}
onPress={openDevice}
/>
// control Cell 外层不是 button;Switch 自己命名
<Cell
title="通知"
extra={{
kind: 'control',
node: (
<Switch
value={notify}
onChange={setNotify}
accessibilityLabel="通知"
/>
),
}}
/>
// static 语义内容使用 text;外层保持普通 View
<Cell title="状态" extra={{ kind: 'text', value: '在线' }} />
// static display 仅作装饰,不能声明 accessibilityText
<Cell
title="品牌"
extra={{ kind: 'display', node: <BrandBadge /> }}
/>
与 Form 的区别
| 用途 | 选哪个 |
|---|---|
| 数据展示 / 跳转 | Cell · List — 白卡 + gap |
| 数据录入 / 表单 | Form — 白卡 + 行间 hairline |
主题键(Tokens)
读取来源:src/components/ui/Cell/styles.ts。
| Token | 来源 | 作用 |
|---|---|---|
c.surface | useColors() | grouped 模式下单行白卡背景色 |
c.foreground | useColors() | 主标题文字色(title / titleFlush) |
c.foregroundSubtle | useColors() | 副标题 / extra 文字色(grouped 模式) |
c.foregroundMuted | useColors() | 副标题 / extra 文字色(flush 模式,另加 opacity: 0.55) |
c.background | useColors() | <List> grouped 模式外容器背景色(浅灰底) |
c.outline | useColors() | flush 模式行间 hairline 分隔线颜色 |
radius.lg | 静态 token | grouped 模式单行卡片圆角 |
radius.xl | 静态 token | <List> grouped 模式外容器圆角 |
space[5] / space[6] / space[3] | 静态 token | 行内 leading↔body↔extra gap(12)/ 行内水平+垂直内边距(14)/ List 行间 gap + 容器 padding(8) |
space[4] | 静态 token | flush 模式行内元素间 gap(cellFlush.gap) |
type.body / type.xs | 静态 token | 主标题 / 副标题字号(grouped 模式) |
fw.medium | 静态 token | 主标题字重 |
StyleSheet.hairlineWidth | React Native | flush 模式分隔线物理宽度(不参与 token 缩放) |