跳到主要内容

布局

布局

页面由 @norbital-ai/ui/layout 中的布局基元组合而成。基元负责几何布局,外壳负责应用区域,每个页面都遵循相同的主体约定。doctor 会在每个页面与记录视图上检查该约定,违反约定的页面无法通过 bolt check。

API 参考:ui/layout

布局基元

意图基元说明
纵向排列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 或一个 Split
  • variant="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 的尺寸标记。