Form Composition
Input · Select · ButtonInput, Select und Button gemischt in einer Größe: eine Filterleiste und ein Geschäftsformular.
BEHAVIOR CONTRACT
Alle drei teilen sich ControlSize (Boxhöhe XS 20 / SM 24 / MD 32 / LG 40), sodass Höhe, Radius, Schrift, Icons und Abstände in jeder Größe übereinstimmen.
RECORDED INTERACTION
Der Harness filtert in der Leiste nach Status und Verantwortlichem und tippt eine Suche, dann füllt er Kunde, Region und Betrag im Formular aus.
pub fn build(scope: *ui.Scope, cx: *ui.Cx) anyerror!*ui.Node {
const a = cx.allocator;
const root = try ui.box(cx, .{ .direction = .column, .gap = 16 }, .{});
tag(root, "story.formcompose.root");
try root.appendChild(a, try heading(cx, "Filter toolbar · same size"));
try root.appendChild(a, try caption(cx, "Input / Select / Button share ControlSize: XS 20 · SM 24 · MD 32 · LG 40 px"));
inline for (sizes) |spec| {
try root.appendChild(a, try filterToolbar(spec, scope, cx));
}
try root.appendChild(a, try heading(cx, "Business form · MD"));
try root.appendChild(a, try orderForm(scope, cx));
return root;
}Der unveränderte Quellcode der Story aus dem Storybook (zenit 5f9add5+wip 2026-09-30). W steht für ui.widgets; col / row / label sind kleine Layout-Helfer des Storybooks.
SelectProps
ui.widgets.Selectfn mountSelect(props: SelectProps, scope: *Scope, cx: *Cx) !SelectMountFeld
Typ
Standard
Beschreibung
options[]const SelectOption&.{}—
placeholder[]const u8"Select an option..."—
widthf32352—
sizeSelectSize.md—
modeSelectMode.single.single—
searchableboolfalse—
show_selected_value_in_searchbooltrueKeep the selected label in a searchable single-select trigger. Set to false for search-first presentations where the trigger should retain its search placeholder while the selected row remains checked.
clearableboolfalse—
leading_icon?svg_assets.Assetnull—
initial_selected[]const usize&.{}—
disabledboolfalse—
max_dropdown_heightf32280—
on_change?core.HandlerRefnull—
InputProps
ui.widgets.Inputfn Input(props: InputProps) InputBuilderFeld
Typ
Standard
Beschreibung
initial_value?[]const u8null初始值。**只在 mount 时读取一次** —— 之后改无效。 首帧之后要写入内容,用 `mountResult()` 拿 `result.state` 再调 `setText()`(2026-07-31 新增)。
growableboolfalse—
placeholder?[]const u8null占位符
placeholder_color?Colornull占位符文本颜色覆盖
input_typeInputType.text.number.email.password.tel.text输入类型
sizeInputSize.md尺寸 (统一 ControlSize,默认 md;外框高度 = padding_y × 2 + font_size × line_height, 默认主题 xs=20 / sm=24 / md=32 / lg=40)
label_text?[]const u8null标签
helper?[]const u8null辅助文本
error_msg?[]const u8null错误信息
requiredboolfalse必填
readonlyboolfalse只读
disabledboolfalse禁用
width?f32null宽度
leading_icon_asset?svg_assets.Assetnull左侧图标
append_text?[]const u8null右侧附加文本(如计数)
append_icon_asset?svg_assets.Assetnull右侧附加图标(如 clear / eye)
embeddedboolfalse嵌入复合控件时仅保留文本编辑能力,由父控件绘制统一 shell。 Select/ComboBox 等组件使用它避免出现双层 border / focus ring。
state_id?u64null状态 ID (用于 StateStore, 如果为 null 则使用自动生成的 ID)
on_change?core.HandlerRefnull值变化回调 2026-07-31 并轨:统一 ?core.HandlerRef(用 cx.strHandlerFrom 构造以拿到文本)。
ButtonProps
ui.widgets.Buttonfn Button(props: ButtonProps) ButtonBuilderFeld
Typ
Standard
Beschreibung
label?[]const u8null标签文本
variantButtonVariant.primary.secondary.ghost.danger.link.field.primary变体
sizeButtonSize.md尺寸
icon_onlyboolfalse图标模式(只显示图标)
loadingboolfalse加载中
disabledboolfalse禁用
blockboolfalse块级(宽度填充) 非 block Button 保持 intrinsic 宽度:父容器交叉轴为 stretch(默认)时按 start 排而不被拉伸;父显式 align_items=.center/.end 时照常居中/靠尾。
on_click?core.HandlerRefnull点击 handler
icon?u32null图标纹理 ID(放在 label 文本左侧)
icon_asset?svg_assets.AssetnullSVG 图标资源(优先于 texture icon)
icon_size?f32null图标尺寸(null = 跟随 control metrics 的 icon_size;应 ≤ 行高)
icon_tint?Colornull图标 tint 颜色(默认跟随 textColor)
on_event?GenericEventCallbacknull通用事件处理(支持 hover/key 等复合事件)
event_context?*anyopaquenull事件上下文
样式覆盖
styleStyleOverride.{}默认态样式覆盖
hover_style?StyleOverridenullhover 态样式覆盖
pressed_style?StyleOverridenullpressed 态样式覆盖
SelectMount
ui.widgets.SelectFeld
Typ
Standard
Beschreibung
wrapper*Node—
—
trigger*Node—
—
panel*Node—
—
state*SelectState—
—
is_open*Signal(bool)—
—
*Node
ui.widgets.Inputmount gibt *Node direkt zurück – hängen Sie es an einen Elternknoten an.
*Node
ui.widgets.Buttonmount gibt *Node direkt zurück – hängen Sie es an einen Elternknoten an.