布局
布局
Bolt 中的应用表面由 布局原语 中的一小组 @norbital-ai/ui/layout 组合而成。原语拥有几何;外壳拥有应用区域与文档滚动;每个表面遵循同一个主体约定。编译时检查强制执行这一点——违反规则的表面会构建失败,而不仅仅是审查不通过。
原语
按布局意图选择原语,而不是按哪个看起来最接近:
| 意图 | 原语 | 说明 |
|---|---|---|
| 纵向节奏 | Stack | 带 gap 的子元素列,可选的 align / justify |
| 一行 | Inline | 子元素的一条水平线 |
| 自动换行组 | Cluster | 会换行的行;标签、徽章、按钮组 |
| 两个自适应区域 | Split | 具名比例( rail 、 sidebar 、 third 、 half 、 wide )与共享折叠令牌 |
| 内禀网格 | Grid | 从 minimum 令牌( compact / card / panel |
| 精确跨度 | Columns + Column | 固定数量(2/3/4/6)并带显式 span |
| 顶部、主体、底部 | Cover | 在可选的 top 与 bottom |
| 可读度量 | Center | 把宽度约束到一个度量( narrow / reading / wide / full |
| 媒体裁剪 | Frame | 固定宽高比( square / portrait / landscape / widescreen |
| 局部滚动 | Bound + Scroll | 永远成对出现——绝不用裸 overflow |
所有原语都接受令牌刻度上的 gap 与 pad ( none 、 xs 、 sm 、 md 、 lg 、 xl )、一个 as 元素(一组流程容器白名单,如 section 、 article 、 main 、 form ),以及各自的具名属性。父级选择布局算法;子级不请求增长。
应用主体约定
每个应用都是一个带 Cover 的 top 标题插槽与恰好一个主体区域:
┌───────────────────────────────────────────────┐
│ Cover top={pageHeading} │
│ ┌─────────────────────────────────────────┐ │
│ │ PageHeader title="Tasks" … │ │
│ └─────────────────────────────────────────┘ │
│ │
│ body region (one of three, below) │
│ ┌─────────────────────────────────────────┐ │
│ │ Scroll name="tasks" inset │ │
│ │ ┌───────────────────────────────────┐ │ │
│ │ │ CollectionTable collection="tasks"│ │ │
│ │ └───────────────────────────────────┘ │ │
│ └─────────────────────────────────────────┘ │
└───────────────────────────────────────────────┘ 合法的主体区域恰好有三种,每一种都同时拥有滚动与应用内边距:
| 主体 | 拥有滚动 + 内边距 |
|---|---|
<Tabs … /> | ,自动 |
<Scroll name="…" inset> ,用于流式内容 | 该 |
<Bound size="full" inset> ,用于单个自滚动组件 | 该 |
<script lang="ts">
import { CollectionTable } from '@norbital-ai/ui/collection-table';
import { Bound, Cover, Scroll } from '@norbital-ai/ui/layout';
import { PageHeader } from '@norbital-ai/ui/page-header';
</script>
<svelte:head>
<title>Tasks</title>
<meta name="bolt:icon" content="lucide:list-todo" />
</svelte:head>
{#snippet pageHeading()}
<PageHeader title="Tasks" description="Everything the team is working on." />
{/snippet}
<Cover as="main" top={pageHeading}>
<Scroll name="tasks-scroll" inset>
<CollectionTable collection="tasks">
{#snippet columns({ Column })}
<Column name="title" />
<Column name="status" />
{/snippet}
</CollectionTable>
</Scroll>
</Cover> 滚动:每个轴一个属主
外壳拥有文档滚动;每个局部滚动区域都是显式的 Bound + Scroll 对。每个祖先链有 每个轴一个滚动属主与一个内边距属主 ;兄弟面板可以拥有各自的。由此得出的规则在编译时强制执行:
- 不允许泛化的
overflow包装器、overflow-hidden裁剪或裸 flex/min-size 滚动链。 - 不允许裸的结构性 flex/grid 容器,也不允许兄弟之间的 margin——间距是原语的
gap. - 不允许字面的
px-4 sm:px-6风格类——应用内边距是原语的inset. - 布局原语上不允许
style属性——使用它的具名属性。 - 裁剪只对文本截断、
Frame媒体或经过审计的弹窗/抽屉边界有效。
无需断点配方的响应式
Split接受collapse(stack、switch、none)与共享的collapseAt令牌,而不是任意宽度。Grid是内禀的:它从minimum令牌按可用空间自动适配单元格。只有在精确数量或跨度重要时才用Columns。Center无需手工媒体查询即可保持阅读宽度。优先使用内禀布局与共享容器查询令牌,而不是视口断点配方。
强制执行,而非建议
编译器在每个表面上校验布局约定:未知布局属性、裸裁剪、滚动链与原语上的内联样式都是诊断项,位于
src/apps/ 之外的编写源码也以同样的方式检查。表单如何融入表面参见 UI 组件 。