# 套件 API —— `dsh/canvas`
画布文件**只能** import 这一个模块说明符:
~~~tsx
import {
Stack, Row, Grid, Divider, CollapsibleSection,
H1, H2, Text, Code,
Card, CardHeader, CardBody, Callout,
Stat, Table, BarChart, TodoList, Progress, KeyValue, Timeline,
Button, Pill,
useState, useEffect, useMemo, useCallback, useRef,
useCanvasState, useCanvasOverlay, useCanvasAction, useHostTheme, useCanvasResource,
} from "dsh/canvas";
~~~
不要 `import React from "react"`,也不要任何其它模块——它们会被 `E_PARSE_IMPORT` 拒绝。
## 设计约定(先读这五条)
1. **只认语义 `tone`,不认颜色**:不要写颜色值、不要写 class、不要写像素间距。颜色与间距由宿主解析,主题切换自动跟随。
2. **受控组件**:`active` / `open` 由调用方给。唯一例外是 `CollapsibleSection` 的 `defaultOpen`。
3. **`tone` 与 `size` 是两条独立的轴**:`tone` 管颜色,`size` 管字号档位。
4. **只加不减**:套件不会删导出或改签名。若某天必须改,会先给 `W_DEPRECATED` 渲染期信号(**不是编译诊断**)并保留至少两个 minor 版本。
5. **字号只从 harness 排版刻度取**:不要在画布里写 `fontSize` / 像素值 / `calc(base * 1.6)`。要新的档位就先改套件,并在「排版标准」里登记。
## 通用类型
~~~ts
type Tone = "neutral" | "info" | "success" | "warning" | "danger";
type Status = "pending" | "in_progress" | "blocked" | "completed" | "cancelled";
~~~
| tone | 语义 | 典型用途 |
|---|---|---|
| `"neutral"` | 中性 / 未知 | "无参考基线"、归档 |
| `"info"` | 信息 | "ours" 系列、一般提示 |
| `"success"` | 通过 | 门禁绿、已完成 |
| `"warning"` | 需注意 | 偏差、进行中、超阈值 |
| `"danger"` | 失败 / 回归 | 门禁红、编译错误 |
`Text` 的 `tone` 额外接受 `"primary" | "secondary" | "tertiary"` 三个便利别名(映射到中性色的三个弱化档)。
---
## 排版标准
画布活在右栏,右栏的正文基准是 **`--dsh-content-font-size-secondary`(13px)**,不是聊天区的 14px。所有字号都取 harness 排版 token 的**长写形式**(`--dsw-font-*-font-size` / `-line-height`):简写 token(如 `--dsw-font-xs-13` = `13px/20px family`)作为 `font-size` 是无效值,会静默退回继承字号——这正是「有大有小」的成因。规则由套件内部执行,画布只选档:
| 用法 | 组件 / prop | token | 实际 |
|---|---|---|---|
| 正文(默认) | `Text` | `--dsh-content-font-size-secondary` | 13 / 20 |
| 小字 | `Text size="small"` | `--dsw-font-xxs-12` | 12 / 18 |
| 注脚 | `Text size="caption"` | `--dsw-font-xxxs-11` | 11 / 14 |
| 大字 | `Text size="large"` | `--dsw-font-s-14` | 14 / 22 |
| 一级标题 | `H1` | `--dsw-font-l-20` | 20 / 28 · 500 |
| 二级标题 | `H2` | `--dsw-font-base-strong-16` | 16 / 24 · 500 |
| 指标数字 | `Stat` | `--dsw-font-l-20` | 20 / 28 · 600 |
| 等宽 / 行内代码 | `Code` | `--dsw-font-markdown-code` + `--ds-font-family-code` | 12 / 19 |
| 表格 | `Table` | 正文档 13 / 20,表头字重 500 | — |
**`Code` 的字族是 `--ds-font-family-code`**。主题里**没有** `--dsw-font-mono`,裸 `var()` 是无效字族,会让代码块退回 UI 字体(已修的 bug,不要再写那个名字)。
不要在画布里写 `style={{ fontSize: ... }}` 或 `calc(...)` 覆盖字号:绕开刻度就会重新变成「有大有小」。
---
## 布局
### `Stack`
纵向 flex。画布根节点通常就是它。
| prop | 类型 | 默认 | 说明 |
|---|---|---|---|
| `gap` | `number` | `12` | 子元素间距(宿主间距单位) |
| `align` | `"start" \| "center" \| "end" \| "stretch"` | — | 交叉轴对齐 |
| `children` | `ReactNode` | — | — |
~~~tsx
Title
Subtitle
~~~
### `Row`
横向 flex。
| prop | 类型 | 默认 | 说明 |
|---|---|---|---|
| `gap` | `number` | `8` | 间距 |
| `wrap` | `boolean` | — | 允许换行(按钮条、Pill 条建议开) |
| `align` | 同 `Stack` | — | 交叉轴对齐 |
| `justify` | `"start" \| "center" \| "end" \| "space-between"` | — | 主轴对齐 |
~~~tsx
Open 12
All 40
~~~
### `Grid`
等宽或自定义列网格。
| prop | 类型 | 默认 | 说明 |
|---|---|---|---|
| `columns` | `number \| string` | `2` | 数字 = 等宽列数;字符串直接作为 CSS `grid-template-columns`(支持 `"minmax(0, 1.1fr) minmax(0, 0.9fr)"`) |
| `gap` | `number` | `16` | 间距 |
| `align` | 同 `Stack` | — | 交叉轴对齐 |
> **并排看内容量。** 卡片里只有「一个数字 + 一句标签」这种短内容——例如顶部的 Stat 概览——就并排:`columns="repeat(auto-fit, minmax(120px, 1fr))"` 会让窄栏自动折成 2 列。承载句子、字段列表或表格的内容(待办 / 详情 / 明细 / 时间线)一律纵向:右栏宽度可变、默认偏窄,把长内容分栏必然挤碎。
~~~tsx
{/* 概览指标:三列等宽 */}
{/* 左右主从:列表 + 详情 */}
...
...
~~~
### `Divider`
1px 分隔线,无 props。
~~~tsx
~~~
### `CollapsibleSection`
折叠区。用来把"历史 / 归档 / 说明"收起来,是保持画布小的主要手段。
| prop | 类型 | 默认 | 说明 |
|---|---|---|---|
| `title` | `string` | 必填 | 折叠区标题 |
| `count` | `number` | — | 渲染成标题后的尾随徽标 |
| `trailing` | `ReactNode` | — | 标题行右端(放说明文字或 Pill) |
| `defaultOpen` | `boolean` | `false` | 非受控初值;这是套件里唯一带内部状态的 prop |
| `children` | `ReactNode` | — | 折叠内容 |
~~~tsx
历史不进默认视图}
>
见 specs 归档。
~~~
---
## 排版
### `H1` / `H2`
两级标题,props 只有 `children`。
~~~tsx
STEP to OBJ remaining gaps
Face count vs OCCT
~~~
### `Text`
正文。
| prop | 类型 | 默认 | 说明 |
|---|---|---|---|
| `size` | `"small" \| "md" \| "large"` | `"md"` | 字号档 |
| `weight` | `"normal" \| "semibold"` | `"normal"` | 字重档 |
| `tone` | `Tone \| "primary" \| "secondary" \| "tertiary"` | — | 颜色 |
| `style` | `CSSProperties` | — | 逃生口;只在确实需要时用 |
| `children` | `ReactNode` | — | — |
~~~tsx
正文。
强调一行
来源与脚注
Hits: Shape
~~~
### `Code`
等宽文本,用于文件路径、命令、标识符。props 只有 `children`。
~~~tsx
crates/occt-topo/src/brep_surface.rs:306
~~~
---
## 容器
### `Card` / `CardHeader` / `CardBody`
卡片。`CardHeader` 是标题行(`trailing` 右对齐),`CardBody` 是内容区(带内边距)。三者按 `Card > CardHeader|CardBody` 嵌套。
| 组件 | props |
|---|---|
| `Card` | `children` |
| `CardHeader` | `trailing?: ReactNode`;`children` |
| `CardBody` | `children` |
~~~tsx
active}>
T-323
未移植的极值搜索窗口
OCCT: ShapeAnalysis_Surface.cxx:1340-1352
~~~
### `Callout`
提示块。宿主也用它渲染编译错误卡(`tone="danger"`)。
| prop | 类型 | 默认 | 说明 |
|---|---|---|---|
| `tone` | `Tone` | `"info"` | 语义色 |
| `title` | `string` | — | 加粗标题行 |
| `children` | `ReactNode` | — | 正文 |
~~~tsx
管线是共享的。补缺失的分支,保住既有基线。
~~~
### `KeyValue`
对齐的「标签 / 值」字段表。详情面板字段超过 3 条时用它:标签落在同一列,值对齐成一条线,比一串 `Text` 更容易扫读。
| prop | 类型 | 默认 | 说明 |
|---|---|---|---|
| `items` | `{ label: string; value: ReactNode; tone?: Tone }[]` | 必填 | 每行的标签与值;`tone` 只影响值的颜色 |
| `columns` | `number` | `1` | 分栏数;字段多且面板宽时可以给 2 |
| `dense` | `boolean` | `false` | 紧凑行距 |
~~~tsx
{t.evidence} },
]}
/>
~~~
---
## 数据
### `Stat`
单个指标块:大号 `value` + 小号 `label`。
| prop | 类型 | 默认 | 说明 |
|---|---|---|---|
| `value` | `string \| number` | 必填 | 主数值 |
| `label` | `string` | 必填 | 说明文字 |
| `tone` | `Tone` | — | 语义色 |
| `hint` | `string` | — | 悬浮说明 |
~~~tsx
~~~
### `Table`
明细表。画布的主力组件。
| prop | 类型 | 默认 | 说明 |
|---|---|---|---|
| `headers` | `ReactNode[]` | 必填 | 表头 |
| `rows` | `ReactNode[][]` | 必填 | 每行的单元格(可以是任意节点,例如放 `