布局
布局
页面由 @norbital-ai/ui/layout 中的布局基元组合而成。基元负责几何布局,外壳负责应用区域,每个页面都遵循相同的主体约定。doctor 会在每个页面与记录视图上检查该约定,违反约定的页面无法通过 bolt check。
布局基元
| 意图 | 基元 | 说明 |
|---|---|---|
| 纵向排列 | Stack | 带 gap 的一列子元素 |
| 单行 | Inline | 单条水平排列的子元素 |
| 换行组 | Cluster | 会换行的一行:标签、徽章、按钮组 |
| 两个自适应区域 | Split | 具名比例与共享的折叠标记 |
| 自适应网格 | Grid | 根据 minimum 标记自动排布单元格 |
| 精确跨度 | Columns / Column | 固定列数与显式跨度 |
| 顶部、主体、底部 | Cover | 在可选的顶部与底部之间居中主体 |
| 可读宽度 | Center | 把宽度限制在一个尺度内 |
| 媒体裁剪 | Frame | 固定宽高比 |
| 覆盖层 | Imposter | 放置在另一元素之上的内容 |
| 局部滚动 | Bound + Scroll | 始终成对出现:一个高度约定及其内部唯一的滚动所有者 |
元素之间的间距由父元素的 gap 决定;没有外边距。由父元素选择布局算法,子元素不请求扩展。
应用主体约定
<!-- src/app/scheduler/schedule/+board.page.svelte (templates/on-demand-services, excerpt) -->
<script lang="ts">
import { bolt } from '$bolt';
import { AppShell } from '@norbital-ai/ui/layout';
import { Board, Table, Tabs } from '@norbital-ai/ui';
const t = bolt.t;
// …
</script>
{#snippet byHelper()}<Board of="visits" by="helper" {lanes} {where} {card} />{/snippet}
{#snippet list()}<Table of="visits" {where} columns={['number', 'slot', 'helper', 'status']} />{/snippet}
<AppShell
icon="lucide:kanban"
title={t('app.schedule.board_title')}
description={t('app.schedule.board_description')}
variant="full"
>
<Tabs
tabs={[
{ name: 'by_helper', title: t('app.schedule.by_helper'), icon: 'lucide:kanban', body: byHelper },
{ name: 'list', title: t('app.schedule.list'), icon: 'lucide:list', body: list }
]}
/>
</AppShell> 每个页面都是一个 AppShell。它的 title、description、icon 与 banner 渲染头图区域,actions 片段放置应用级的范围选择与操作。它的主体恰好是一个区域:
variant="page"(默认)——一个视图,由它负责滚动;外壳为主体添加内边距variant="full"搭配Tabs——每个标签页主体渲染一个视图、一个Scroll inset或一个Splitvariant="full"搭配<Scroll name="…" inset>——流式内容
滚动:每个方向一个所有者
视图负责自己的滚动:Table 的行、Board 的泳道、Form 的字段。外壳只在万不得已时滚动,同一方向上从不嵌套两个滚动区域。自定义滚动区域始终成对出现——一个 Bound 提供高度约定,其内部一个 Scroll。
<!-- one explicit scroll region: a height contract and one scroll owner inside it -->
<Bound size="full">
<Scroll name="Quote lines">
…
</Scroll>
</Bound> doctor 会强制执行:
- 不使用原始布局类、结构性的 flex 或 grid 容器,也不在兄弟元素之间使用外边距:请使用布局基元及其
gap。 - 不要在已经自行滚动的视图(
Table、Board、Matrix、Form、Tabs)外面包裹Scroll。 - 内边距只有一个所有者:区域的滚动容器只应用一次应用内边距,页面不自行编写边缘内边距。
- 视图上不使用固定的最小高度;高度来自
Bound的尺寸标记。