Form Composition
Input · Select · ButtonInput, Select y Button combinados en un mismo tamaño: una barra de filtros y un formulario de negocio.
BEHAVIOR CONTRACT
Los tres comparten ControlSize (altura de caja XS 20 / SM 24 / MD 32 / LG 40), así que altura, radio, tipografía, iconos y espaciado se alinean en cada tamaño.
RECORDED INTERACTION
El harness filtra por estado y responsable y escribe una búsqueda en la barra; luego rellena el cliente, la región y el importe en el formulario.
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;
}El código literal de la story en el Storybook (zenit 5f9add5+wip 2026-09-30). W es ui.widgets; col / row / label son pequeños helpers de layout del Storybook.
SelectProps
ui.widgets.Selectfn mountSelect(props: SelectProps, scope: *Scope, cx: *Cx) !SelectMountCampo
Tipo
Predeterminado
Descripción
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) InputBuilderCampo
Tipo
Predeterminado
Descripción
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) ButtonBuilderCampo
Tipo
Predeterminado
Descripción
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.SelectCampo
Tipo
Predeterminado
Descripción
wrapper*Node—
—
trigger*Node—
—
panel*Node—
—
state*SelectState—
—
is_open*Signal(bool)—
—
*Node
ui.widgets.Inputmount devuelve *Node directamente: añádelo a un nodo padre.
*Node
ui.widgets.Buttonmount devuelve *Node directamente: añádelo a un nodo padre.