Form Composition
Input · Select · ButtonInput, Select et Button mélangés à une même taille : une barre de filtres et un formulaire métier.
BEHAVIOR CONTRACT
Les trois partagent ControlSize (hauteur de boîte XS 20 / SM 24 / MD 32 / LG 40), si bien que hauteur, rayon, typographie, icônes et espacements s'alignent à chaque taille.
RECORDED INTERACTION
Le harness filtre par statut et responsable et saisit une recherche dans la barre, puis remplit le client, la région et le montant dans le formulaire.
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;
}Le code source tel quel de la story dans le Storybook (zenit 5f9add5+wip 2026-09-30). W désigne ui.widgets ; col / row / label sont de petits helpers de mise en page du Storybook.
SelectProps
ui.widgets.Selectfn mountSelect(props: SelectProps, scope: *Scope, cx: *Cx) !SelectMountChamp
Type
Défaut
Description
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) InputBuilderChamp
Type
Défaut
Description
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) ButtonBuilderChamp
Type
Défaut
Description
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.SelectChamp
Type
Défaut
Description
wrapper*Node—
—
trigger*Node—
—
panel*Node—
—
state*SelectState—
—
is_open*Signal(bool)—
—
*Node
ui.widgets.Inputmount renvoie directement *Node : ajoutez-le à un parent.
*Node
ui.widgets.Buttonmount renvoie directement *Node : ajoutez-le à un parent.