Astryx Layout Regions 家族契约:Section、Layout 五槽位与 Toolbar 的页面结构化区域体系
【免费下载链接】astryxAn open source design system that's fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx
导读
Astryx 将"页面或受限工作区在填充产品内容之前,应当具备可预测的结构化区域"沉淀为一条独立的组件家族契约。本文围绕 docs/families/layout-regions.md 展开,系统讲解三个核心成员——通用区域Section、五槽位原语Layout(及其 Header/Content/Footer/Panel 区域)与上下文动作栏Toolbar——的成员资格边界、所有权划分、十二条跨组件不变量(FR1–FR12)以及可验证的采纳现状。读完本文,你将掌握:哪些组件有权声称"结构性区域"、内边距与分隔线在区域之间如何归属、contentWidth在无面板/单面板/双面板三种组合下的几何行为、滚动与地标语义为何必须显式声明,以及如何用仓库中的测试文件验证每一项契约。
一、契约意图:先定区域骨架,再填充产品内容
契约的核心意图只有一句话:页面结构应该先于产品内容存在。构建者可以使用通用的Section、五槽位的Layout原语、或带语义的Toolbar,而无需为每个页面自造一套 inset(内缩)、边界(boundary)、区域方向(region direction)或内容归属(content ownership)规则。
从 Layout.tsx 的实现 可以清楚地看到这条意图的落地方式:Layout通过AreaProvider把header/start/content/end/footer五个区域分别包裹在LayoutAreaContext中,区域类型定义在 LayoutAreaContext.ts:
export type LayoutArea = | 'header' | 'footer' | 'content' | 'start' | 'end' | null;任何渲染在槽位内的子组件都能通过use(LayoutAreaContext)感知自己身处哪个区域,从而自动决定分隔线朝向与内边距。而页面外壳(page shell)本身不属于 Layout——契约明确约定AppShell负责将低层级区域与应用导航组合成完整页面外壳。Layout只是一块"可复用的区域语法",不拥有页面语义。
二、成员资格规则:什么才配叫"结构化区域"
成员、协作者与排除项
| 类别 | 组件 | 判定理由 |
|---|---|---|
| 成员 | Section;Layout及其Header、Content、Footer、Panel区域;Toolbar | 主要公开用途就是定义一个稳定的结构性区域:通用页面/内容区域、五槽位拓扑中的具名位置、或上下文动作区域 |
| 协作者 | family:layout-primitives(任意子排列工具)、architecture:container-padding(inset 与 bleed 系统)、useResizable/ResizeHandle(面板尺寸)、AppShell(页面外壳)、组件主题(视觉目标) | 提供排列、内缩、缩放、组合与视觉能力,但不改变区域所有权 |
| 排除项 | Stack、Grid、Center及其修饰符;FormLayout;Card;AppShell;Table;Divider | 它们要么只是"排列任意子元素"(不声称区域),要么拥有独立的语义(离散条目表面、页面外壳、结构化数据、分隔) |
契约给出了一条极其明确的判定准则,值得单独划重点:
A component does not join because it happens to render a flex row, a padded wrapper, or a header-like visual treatment.
也就是说,仅仅因为一个组件渲染了 flex 行、带内边距的包裹层、或长得像 header,它不因此加入本家族。这与 docs/families/layout-primitives.md(任意子排列工具)和 docs/architecture/container-padding.md(共享 inset 系统)形成清晰分工:排列归排列、内缩归内缩、区域归区域。
为什么这个边界重要(DEC-1)
决策记录DEC-1(Decider:cixzhang,2026-08-30)指出:Section、Layout 及其具名区域、Toolbar 组成 layout-regions 家族,而 Stack/Grid/Center 及其修饰符由独立的 layout-primitives 所有者管理。这样做的意义是:让共享区域规则聚焦于结构性边界,而不是把每一个 flex/grid 容器都变成页面区域。
三、所有权划分:每个成员管什么
| 所有者 | 管辖内容 |
|---|---|
Section | 通用"绘制型"页面/内容区域:variant、选中的分隔线边缘、组件内边距、嵌套 Section 行为 |
Layout | 五槽位拓扑、槽位存在性、区域位置、共享外/内 inset、高度模式、内容宽度传播、默认 header/footer 分隔线上下文。不拥有页面外壳 |
LayoutHeader/LayoutContent/LayoutFooter/LayoutPanel | 各自的区域元素、可选地标角色/名称、局部内边距、区域特定的尺寸或滚动行为 |
Toolbar | 上下文动作区域:start/center/end 泳道、toolbar 语义、键盘移动、尺寸级联。其绘制表面、variant 与选中分隔线边缘委托给 Section |
architecture:container-padding | 继承的 inset 与 bleed 机制 |
useResizable/ResizeHandle | 缩放状态与交互;LayoutPanel只接受它们提供的当前尺寸作为替代宽度所有者 |
从源码看,"Toolbar 委托绘制表面给 Section"是字面级的真委托:Toolbar.tsx 直接以<Section>为根节点渲染,把variant、dividers透传给 Section,同时把paddingBlock按尺寸映射为紧凑的垂直间距:
<Section ref={ref} variant={variant} paddingBlock={defaultBlockPaddingForSize[size]} dividers={dividers} xstyle={xstyle} className={className} style={style}>而 Section.tsx 的实现 则展示了它的三重职责:外层<div>负责嵌套逃逸(负 margin 抵消父容器 padding)、内层<div>通过container()解析出逻辑边缘变量、variant 与 dividers 决定绘制。Section 的三种 variant 对应三种背景(见 Section.tsx):
section:--color-background-surface表面色transparent:完全透明muted:--color-background-muted弱化色
四、规范概念表:区域语法的七个维度
契约用一张表定义了所有成员共享的规范概念(concepts)及其稳定级别:
| 概念 | 值/状态 | 默认语义 | 稳定性 |
|---|---|---|---|
| generic region | Section | 相关的页面/内容区域,带可选绘制处理 | current |
| five-slot topology | header, start, content, end, footer | Layout 按逻辑阅读方向放置已提供的区域 | current |
| contextual actions | start, 可选 center, end | Toolbar 在受限内容区域内分组控件 | current |
| outer and inner inset | Layout 边缘或相邻区域边缘 | Layout 区域使用与其接触边缘相匹配的 inset | current |
| boundary | 无,或分隔线 | 成员只能绘制其组件契约暴露的边缘 | current |
| region scroll | clipped、scrollable、或页面所有(在暴露处) | LayoutContent 与 LayoutPanel 拥有各自的当前 overflow 选择 | component-owned |
| landmark | 无显式角色,或调用方提供的角色与标签 | 区域不会从视觉位置推断页面级地标语义 | current |
| region size | intrinsic、fixed、fill/auto、capped、或 resize 驱动(在暴露处) | 每个成员拥有其适用的尺寸轴 | component-owned |
| responsive adaptation | 保留、省略、或由调用方组合替换 | 不存在共享的自动区域切换 | caller/component-owned |
这张表最重要的信号是:区域滚动、区域尺寸、响应式适配三者都是 component-owned / caller-owned,家族本身不设置任何断点、不自动切换区域、不把滚动容器强加给所有成员(FR7、FR9)。
五、跨组件不变量(FR1–FR12):区域行为的十二条宪法
这是契约的绝对主体,任何成员行为都不得违反。以下逐条展开,并附上仓库实现证据。
FR1 — 区域拥有结构,而非产品含义
成员建立空间边界并渲染调用方内容,但不采纳内容的产品语义、状态或组件契约。也就是说,把购物车、消息流或设置表单放进LayoutContent,Layout 不会因此知道它们是购物车、消息流或设置表单。这是"结构区域"与"业务组件"的分水岭。
FR2 — 区域方向是逻辑的
start 与 end 跟随书写方向(LTR 时 start 在左、RTL 时在右)。分隔线放置与面板相邻性使用同一个逻辑区域位置。源码中这一条体现得非常彻底:LayoutPanel的分隔线样式使用borderInlineEnd/borderInlineStart(见 LayoutPanel.tsx),Section 的边分隔使用borderInlineStart/borderInlineEnd(见 Section.tsx),全部是 CSS 逻辑属性而非物理 left/right。
FR3 — Layout 依据槽位存在性选择几何
Header/Content/Footer/Panel 在接触 Layout 边界处应用外 inset,在接触另一个区域处应用内 inset。LayoutContent保持其根节点作为 scroll、padding 与直接子元素的唯一所有者。被省略的槽位不渲染区域包裹层——这一点由 Layout.test.tsx 直接验证:"does not render an area wrapper for an omitted slot"。
Layout 根节点的内层结构(见 Layout.tsx)是理解 FR3 的关键:
<div layoutOuter + fill/auto> ← 负 margin 逃逸容器 padding <div layoutInner + stack vertical> ← 重置容器 padding 变量 <AreaProvider area="header">{header}</AreaProvider> <div middle + stack horizontal> ← 中间行:start + content + end <AreaProvider area="start">{start}</AreaProvider> <div stackItem fill>{content}</div> <AreaProvider area="end">{end}</AreaProvider> </div> <AreaProvider area="footer">{footer}</AreaProvider> </div> </div>layoutOuter用calc(-1 * var(--container-padding-*))的负 margin 逃逸外层容器 padding;layoutInner把四个--container-padding-*变量归零,保证后代不会继承错误的 inset。需要说明的是:FR3 同时记录了一个已命名的符合性缺口(conformance gap)——继承几何重新发布(republish)给全出血(full-bleed)后代的完整一致性,仍受限于 docs/architecture/container-padding.md 中描述的 LayoutContent/LayoutPanel 现状。
FR4 — 显式区域内边距取代自动 inset
Section 发布其组件 inset;Layout 把外/内 inset 分发到具名槽位;区域的显式 padding 或零 padding 路径选择各自的局部样式。这一条并不宣称每个自动 Layout 边缘当前都精确地重新发布了一条匹配的后代变量——这是刻意为之的诚实边界。
从 LayoutContent.tsx 可以清楚看到 inset 的"外/内"切换:无 start 面板时noStart把paddingInlineStart与两个 inline 容器变量设为--layout-padding-outer-x;无 header 时noHeader把paddingBlockStart设为--layout-padding-outer-y。padding={0}则走fullBleed路径,把所有边与变量归零。
FR5 — 边界所有权由调用方选择(组合允许两个所有者时)
LayoutHeader与LayoutFooter各自拥有自己的隐含边缘;Section 与 Toolbar 暴露所选边缘;LayoutPanel可以绘制其面向内容侧的边缘,而相邻的ResizeHandle也可能绘制同一条线——当前代码并不阻止两者同时绘制。因此契约给出硬性组合规则:
可缩放面板组合必须在相邻
ResizeHandle拥有分隔线时设置LayoutPanel hasDivider={false},否则会出现双线伪影。
这一规则在 LayoutPanel.tsx 的 hasDivider 注释 中有完全一致的提示,验证映射表也承认"尚无渲染测试阻止 LayoutPanel 与相邻 ResizeHandle 同时绘制分隔线"。
FR6 — 分隔线缺失可能改变间距
Layout 区域当前会折叠与缺失 header/footer/panel 分隔线关联的内部间距;显式分隔线则保留围栏边界。实测依据是 LayoutContent.tsx 中的祖先选择器:
paddingBlockStart: { default: `var(--layout-padding-inner-y, ...)`, [stylex.when.ancestor(':has(> .astryx-layout-header:not([data-divider]))')]: 0, }当 header 没有分隔线时,内容区顶部内边距折叠为 0,实现无缝视觉衔接;footer 同理。LayoutPanel的无分隔线路径还会通过collapseEnd/collapseStart负 margin 向内容侧折叠间距(见 LayoutPanel.tsx)。
FR7 — 滚动是显式且局部的
LayoutContent与LayoutPanel拥有各自的isScrollable行为;Section 与 Toolbar不会因为家族成员身份而成为滚动容器。Layout height="fill"容纳区域滚动;height="auto"让所在页面增长。
两个组件的滚动默认值都是true,且都支持isScrollable={false}以支持父级/页面滚动与 sticky 后代(例如StickyElement)。高度模式的实现位于 Layout.tsx 的 fill/auto 样式:fill通过calc(100% + 2x block padding)补偿负 block margin,auto仅设minHeight: 100%。
FR8 — 地标语义保持显式
视觉上的具名位置不会自动分配banner、main、navigation、complementary、contentinfo角色。调用方必须提供受支持的角色与标签。源码中这些角色只是透传的 props:LayoutContent的role/label(见 LayoutContent.tsx)、LayoutPanel的role(注释明确建议顶层布局才用navigation/complementary)、LayoutHeader的role(建议仅全站级 header 用banner)、LayoutFooter的role(建议仅全站级 footer 用contentinfo)。LayoutSlots.test.tsx对"提供的角色与标签能到达 Header/Content/Footer/Panel 元素"有专门断言。
FR9 — 响应式替换不是自动的
Section 与 Toolbar 保持当前呈现;Layout 渲染调用方提供的区域。当宽度变窄时,由 AppShell、产品组合或其他组件特定的所有者决定省略或替换哪个区域。家族不设置任何断点(与 AV7 一致)。
FR10 — 缩放所有权保持委托
带resizable的LayoutPanel使用 hook 提供的当前尺寸,而不是自己的widthprop。区域家族不重新定义 snapping、持久化、折叠、键盘或指针行为。实现只有一行:LayoutPanel.tsx 第 227 行:
const effectiveWidth = resizable ? resizable._size : width;配合useResizable({ defaultSize: 250, minSize: 200 })与相邻<ResizeHandle resizable={sidebar.props} />的组合模式即可获得可拖拽侧栏。
FR11 — 主题保持组件所有
结构性成员身份不会自动创建家族级主题目标。当前.doc.mjs元数据记录已发布的 targets 与能力,运行时themeProps()输出它们,跨组件规则由 docs/architecture/component-theming-surface.md 拥有。组件 spec 在存在时可添加可选的 anatomy-mapping 元数据。
FR12 — 内容宽度把滚动条保持在开放的 content 边缘
这是关于contentWidth的完整几何规则,也是契约中实现最精细的一条:
- 无面板时:
LayoutContent横跨可用中间区域,并通过上下文感知的 inline inset 对齐直接子元素; - 恰好一个面板时:该面板保持对齐居中框架,而
LayoutContent延伸穿过对侧开放边缘;start/end 镜像情形使用同一条规则; - 两个面板都有时:完整中间组合保持受约束(constrained);
- 百分比与 intrinsic 宽度、以及裸变量:因为它们无法安全共享同一个对齐基础,所以保持受约束组合;
calc(var(...))是显式的长度值变量路径,可据此选择进入边缘滚动。
实现侧的判定函数supportsInternalContentWidthAlignment位于 Layout.tsx:数字与无单位0直接支持内部对齐;字符串中一旦出现%即拒绝;calc|min|max|clamp前缀和"数字+单位"的形式支持内部对齐。与之配套,LayoutContent.tsx 的 constrained 系列样式 通过max(容器内边距, (100cqi - 对齐宽度)/2 + 容器内边距)这样的 gutter 计算,在内容区自己仍是 scroll/padding 所有者的前提下,把直接子元素对齐到contentWidth。
六、允许的组件差异(AV1–AV7):家族内的合理变化
| 编号 | 差异维度 | 内容 |
|---|---|---|
| AV1 | 区域强度 | Section 是通用区域;Layout 成员是五槽位原语中的位置感知槽位;Toolbar 是语义动作栏 |
| AV2 | 表面处理 | Section 与 Toolbar 可用 Section variants 与选中边缘;Layout 区域用现有分隔线与 padding 处理。家族成员身份不统一它们的视觉 API |
| AV3 | 区域内容 | header、footer、panel、content 与 toolbar 泳道可容纳其组件契约允许的任何内容 |
| AV4 | 尺寸模型 | Layout 拥有 fill/auto 容器高度与内容宽度传播;单个区域拥有适用的 height/width/padding/scroll props;Section 拥有 box-size props |
| AV5 | 交互 | Toolbar 拥有 roving focus 与键盘提示;其他成员不增加 toolbar 行为;Resizable 拥有缩放交互 |
| AV6 | 组合机制 | 成员可使用 Stack 工具、直接 CSS 布局、context 或组件组合。可观察的区域契约是"拥有的表面" |
| AV7 | 响应式策略 | 调用方可省略、替换或外部适配区域。家族不设断点 |
AV5 的 Toolbar 键盘行为在 Toolbar.tsx 中落地:useListFocus提供 roving tabindex 与方向键导航(itemSelector: 'button, input, [tabindex]'、hasRovingTabIndex: true、hasCaretGuard: true),useKeyboardHint提供键盘提示;同时通过SizeProvider把size(sm/md/lg)级联给 Button、TextInput、TabList、Selector 等子组件,使sm(28px 元素)、md(32px)、lg(36px)的控件尺寸自动对齐。
Toolbar 的泳道布局也值得展开(见 Toolbar.tsx 的样式):
- 只有 start/end(两槽位):
flex+space-between;只有 start 时让它flex: 1 1 0%填充,只有 end 时用marginInlineStart: auto推到末尾; - 提供 centerContent(三槽位):切换为 CSS Grid
gridTemplateColumns: 1fr auto 1fr,start/end 泳道附加edgeCompSlot.inset(...)边缘补偿,使 ghost 按钮、tab 等在容器边缘对齐时保持均匀视觉间距。
七、代表性矩阵:成员状态 × 共享不变量 × 刻意差异
| 成员与状态 | 共享不变量 | 刻意差异 |
|---|---|---|
| Section / default, transparent, muted | 拥有一个通用区域并发布其应用的 inset | variant 与选中分隔线边缘是 Section 局部的 |
| Layout / 仅内容 | 槽位存在性选择 Layout 边缘 inset | 有contentWidth时,LayoutContent 横跨中间区域、内部对齐子元素、滚动条保持在 Layout 边缘 |
| Layout / 全部区域 | start/end 保持逻辑方向;具名区域接收外/内 inset | 恰好一个面板时内容延伸到对侧开放边缘;双面板时完整组合保持受约束 |
| LayoutHeader / LayoutFooter / 继承分隔线 | 一个边界所有者 + 显式地标语义 | 父级defaultHasDividers提供默认值;局部false可覆盖它 |
| LayoutContent / scrollable 或 page-owned | 区域拥有当前 overflow 选择 | isScrollable={false}支持父级/页面滚动与 sticky 后代 |
| LayoutPanel / fixed 或 resize 驱动 | 面板位置选择边缘处理 | useResizable 可取代宽度所有权;ResizeHandle 保留交互所有权 |
| Toolbar / 两或三泳道 | 上下文动作区域将其表面委托给 Section | center 内容切换内部排列;toolbar 独自拥有键盘行为 |
Header/Footer 的分隔线解析顺序值得给出精确规则(见 LayoutHeader.tsx 与 LayoutFooter.tsx):
const resolvedHasDivider = hasDivider ?? dividerCtx?.defaultHasDividers ?? false;即:显式hasDivider优先 → 父 Layout 的defaultHasDividers其次 → 默认false,最终结果还会写入data-divider属性,供 FR6 的祖先选择器做间距折叠判断。
八、采纳与例外:当前现状与已知缺口
契约用一张"现状 + 缺口"表记录每个成员的实际采纳程度。这张表里列出的都是已发布行为与验证缺口,不是可以在文档 PR 中静默修改的已批准例外:
| 组件或关注点 | 采纳情况 | 当前缺口或例外 |
|---|---|---|
| Section | 通用区域与 container-padding 发布者 | 嵌套逃逸始终是 inline 方向,但 block 轴仅作用于 first/last child |
| Layout 容器 | 五槽位区域所有者,带槽位 contexts | 仅内容的 LayoutContent 保持全宽并通过 inset 对齐直接子元素;单面板时面板保持对齐而内容延伸到对侧开放边缘;双面板或 intrinsic 宽度时中间组合保持受约束。测试覆盖拓扑,浏览器证据覆盖镜像对齐与滚动条位置 |
| LayoutContent | 读取槽位存在性并应用外/内 inset | 无 start 路径把外值写入两个 inline 几何变量,而无 end 路径只改 padding 不更新匹配变量 |
| LayoutPanel | 应用位置感知 padding,可接受 resize 驱动宽度 | 自动 Layout 边缘 padding 遗留基线内几何变量;相邻ResizeHandle hasDivider可能双线,除非调用方设置面板hasDivider={false} |
| LayoutHeader/Footer | 区域特定 inset、边界与地标 | 跨区域计算样式对等性未由单一浏览器矩阵覆盖 |
| Toolbar | Section 支撑的表面 + toolbar 行为 | inline inset 与边缘补偿跟随所组合 Section 的当前 padding 上下文 |
| 响应式区域 | 调用方/AppShell 组合 | LayoutPanel 目前没有共享的响应式可见性契约 |
九、变更耦合:改动一个区域时该碰哪里
契约明确规定了变更联动范围,防止家族契约被随意膨胀:
- 新增或修改结构区域组件:检查成员资格,仅当共享边界改变时才更新本契约;
- 改 Layout 槽位存在性、区域上下文、inset 分发、分隔线继承、高度模式或内容宽度传播:更新对应区域测试与浏览器证据;
- 改 Section padding、嵌套逃逸、variant 或分隔线行为:更新其组件契约,并在继承几何变化时评审 docs/architecture/container-padding.md;
- 改 Toolbar 泳道、语义、键盘行为或尺寸级联:属于 Toolbar 自身工作;若改变其 Section 支撑表面或 inset 关系,还需评审本家族与 container-padding 架构;
- 改 LayoutPanel 缩放集成:评审 Resizable 的组件契约(即
packages/core/src/Resizable/useResizable.ts),但不把缩放机制复制进本家族; - 新增响应式隐藏/替换行为:需要组件或更高级别所有者与兼容性证据——家族成员身份并不授权新增断点 prop。
十、验证映射:每条不变量由什么证据背书
契约对每条不变量给出了具体的验证证据与缺失证据,这是"事实准确"的最高体现。核心要点如下:
| 契约 | 验证手段 | 证据证明了什么 | 缺失证据 |
|---|---|---|---|
| FR1 | Section/Layout/Toolbar 渲染与槽位测试 | 调用方内容渲染在区域结构内;Toolbar 暴露当前泳道 | 测试未独立证明产品语义仍归调用方所有 |
| FR2 | 逻辑属性源码审查 + Layout 区域上下文测试 | start/end 槽位按逻辑识别;源码选择逻辑分隔线/padding 属性 | 无真实浏览器 LTR/RTL 几何矩阵覆盖每个区域 |
| FR3 | Layout.test.tsx 与 childrenAsContent.test.tsx | 槽位存在性与内容优先级被固定;省略的槽位不渲染区域提供者 | 测试未断言计算后的外/内 inset 或精确后代几何;发布变量不匹配是已命名的符合性缺口 |
| FR4 | Section padding 类集测试 + Layout 区域源码 | Section 的显式边缘覆盖移动匹配变量;区域源码有自动/显式/零 padding 分支 | Layout 测试多数断言接受/渲染,而非计算 padding 或自动变量对等性 |
| FR5, FR6 | Layout.test.tsx 与 LayoutSlots.test.tsx | header/footer 分隔线默认与显式 false 覆盖反映在data-divider;源码含折叠样式 | 无渲染测试阻止 LayoutPanel 与相邻 ResizeHandle 双线,或证明计算折叠间距 |
| FR7 | LayoutContent/LayoutPanel 源码与局部类/渲染测试 | 两个区域暴露当前isScrollable分支;Layout 暴露 fill/auto 状态 | 无浏览器测试证明每个 shell/区域组合下的滚动所有权 |
| FR8 | LayoutSlots.test.tsx 地标断言 | 提供的角色与标签到达 Header/Content/Footer/Panel 元素 | 无家族级无障碍树测试覆盖重复地标 |
| Layout content width | contentWidth.test.tsx 与 Storybook 滚动条位置状态 | header/footer 保留对齐包装器;无面板 LayoutContent 拥有全宽滚动区域;单面板任一侧镜像内容穿过对侧开放边缘;双面板保持组合受约束 | 真实浏览器证据验证四种面板状态下的计算内容对齐与滚动条位置 |
| Toolbar 差异 | Toolbar.test.tsx | 泳道、role/name/orientation、Section variant 委托、当前键盘行为有聚焦断言 | 无计算 inset/边缘补偿测试覆盖非默认父 padding |
| FR10 | LayoutSlots.test.tsx 中的 LayoutPanel 覆盖 | hook 提供的_size覆盖固定 width prop | 缩放交互、持久化、snapping、折叠仅由 Resizable 自身测试验证 |
契约同时给出了一个诚实的总结论:当前测试证明的是选定的结构、属性、回调与类/样式分支,并不证明每种 Section/Layout/Toolbar/ResizeHandle 组合下的单一计算视觉对齐——采纳表明确命名了这些限制。
十一、关键决策记录(DEC-1 / DEC-2 / DEC-3)
三条决策分别锁定了家族边界、外壳归属与内容宽度几何:
DEC-1 — 结构区域与组合原语分属不同所有者(cixzhang,2026-08-30):Section、Layout 及其具名区域、Toolbar 构成 layout-regions 家族;Stack/Grid/Center 及修饰符由独立的 layout-primitives 所有者管理。目的是把共享区域规则聚焦于结构性边界,避免每个 flex/grid 容器都变成页面区域。
DEC-2 — AppShell 拥有页面外壳(cixzhang,2026-08-31):AppShell 拥有页面外壳与应用导航组合;Layout 是在页面或有界容器内排列header、start、content、end、footer的通用五槽位原语;container-padding 参与者保留各自组件所有权。
DEC-3 — 仅内容的宽度对齐保持在内容滚动口内(cixzhang,2026-09-09):无面板时 LayoutContent 横跨中间区域并通过上下文 inline inset 对齐直接子元素;单面板时面板保持对齐居中框架而内容穿过对侧开放边缘,start/end 逻辑镜像;双面板或百分比/intrinsic/未解析裸变量(无法安全共享同一个 CSS 算术基础)时,完整中间组合保持受约束;保证能解析为长度值的变量用calc(var(...))选择进入边缘滚动。
十二、开放问题与内容边界
契约的开放问题(Open questions)为空——采纳表中的可检查源码与浏览器工作属于工程任务,而非未解决的家族策略。
内容边界(Content boundary):本记录只拥有共享的结构性区域行为。它不重复组件 prop 表、不定定义任意子排列、不拥有产品或导航语义、不规定视觉设计、不分配响应式断点、不重新定义缩放交互、不拥有组件主题的 anatomy 与 targets。理解这条边界,才能正确地把它当作"区域语法"来使用,而不是当作"万能布局引擎"。
延伸阅读
- 区域 inset 的底层协议:docs/architecture/container-padding.md
- 任意子排列工具(排除项):docs/families/layout-primitives.md
- 组件公共 API 规范:docs/architecture/public-component-api.md
- 跨组件主题规则:docs/architecture/component-theming-surface.md
- 核心实现:Layout.tsx、Section.tsx、Toolbar.tsx
- 关键测试:Layout.test.tsx、LayoutSlots.test.tsx、contentWidth.test.tsx、Section.test.tsx、Toolbar.test.tsx
【免费下载链接】astryxAn open source design system that's fully customizable and agent ready项目地址: https://gitcode.com/GitHub_Trending/as/astryx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考