Cell · List 列表行
每行独立白卡,行间 8px gap。不用 border-bottom 分隔——这是 Unif 列表的核心规则(与 iOS 17 / 微信新版一致)。
实时预览
下方渲染的就是 src/components/ui/Cell/Cell.tsx 本体,通过 react-native-web 翻译成浏览器节点。
设置
客户
disabled · 不可点击 / 视觉变灰
云同步
未登录
通知(开发中)
即将开放
视觉规范
| 元素 | 规则 |
|---|---|
| 整组容器(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 } from '@unif/react-native-design';
<List>
<Cell title="主题" extra="跟随系统" arrow leading="settings" onPress={...} />
<Cell title="通知" arrow leading="bell" />
<Cell title="拜访提醒" desc="按计划自动推送" extra={<Switch value={...} onChange={...} />} />
</List>
{/* 平铺式(无浅灰背景容器,用于嵌套在其它白卡内)*/}
<List flush>
<Cell title="客户名" desc="地址" extra="¥1,200" arrow />
</List>
API
<Cell>
| Prop | Type | 默认 | 说明 |
|---|---|---|---|
title | ReactNode | — | 主标题(必填);string 自动套 <Text> + numberOfLines=titleLines,传 ReactNode 完全自定义 |
titleLines | number | 1 | title 为 string 时的 numberOfLines(如新闻标题传 2) |
desc | ReactNode | — | 副标题 / 描述;string 自动 numberOfLines=2 |
extra | ReactNode | — | 右侧 slot — 文字、Tag、Switch、Stepper 等 |
arrow | boolean | false | 显示右侧 chevron-right(danger 时不渲染) |
leading | IconName | ReactNode | — | 左侧 icon 名(grouped 渲染 22px 裸 icon / flush 渲染 28×28 橙盒 + 16px icon)或自定义节点(如 Avatar) |
onPress | () => void | — | 点击回调(提供时启用按压态;不传则为纯展示 <View>) |
disabled | boolean | false | 仅对带 onPress 的 cell 有效,true 时 onPress 不触发 + 整体半透明 + a11y disabled |
danger | boolean | false | 危险态:icon 盒子 + 标题用 error 红,且不渲染 arrow(常用于退出登录 / 删除等) |
style | StyleProp<ViewStyle> | — | 额外样式覆盖(合并到 cell 容器的 style 数组) |
testID | string | — | E2E / 测试定位 |
accessibilityLabel | string | title(string 时) | 显式 SR 朗读文案;title 传 ReactNode 时务必显式传 |
accessibilityHint | string | — | SR 行为说明 hint(≤8 字,如「查看详情」);onPress 缺省时无意义 |
<List>
| Prop | Type | 默认 | 说明 |
|---|---|---|---|
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、types.ts。
a11y 语义是条件性的:仅当传了 onPress 时,Cell 才用 <Pressable> 包裹并声明语义;不可点的纯展示 Cell 渲染为 <View>,无 a11y 角色。
- 默认
accessibilityRole:可点时为'button'(onPress缺省则无角色)。 - a11y props(仅
onPress时生效):accessibilityLabel取accessibilityLabel ?? (typeof title === 'string' ? title : undefined)—— 即title为字符串时自动作 label;title传 ReactNode(自定义渲染)时务必显式传accessibilityLabel,否则 SR 用户听不到内容。accessibilityHint可选,说明 tap 后行为(短句,如「查看详情」)。 - 状态语义:
disabled=true时上报accessibilityState={{ disabled: true }}(仅对带onPress的 cell 有效)。
// string title 自动作 label
<Cell title="主题" extra="跟随系统" arrow onPress={...} />
// ReactNode title 必须显式给 label
<Cell
title={<CustomTitle />}
accessibilityLabel="客户:东方便利店"
accessibilityHint="查看详情"
onPress={...}
/>
与 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 缩放) |