复杂选择器 · ComplexSelector
用更丰富的选项结构表达复杂选择。
安装与前置条件
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
需要 React 19+,按顺序导入基础 CSS 和主题,并设置 Theme Provider。参见开始使用。
示例与使用方式
- 复杂选择器 · 综合展示 1:首先体验这个示例,再查看代码理解组件的组合方式。
- 复杂选择器 · 交互示例 2:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
- 复杂选择器 · 搜索 3:可交互查看这一用法的行为,复制代码时保留它实际使用的属性与导入。
API 与上游详细参考
优先使用 0.7.1 官方中文说明;上游尚未提供中文的条目保留英文。属性名、类型及默认值保持原生 API,不把示例状态名称当成组件属性。
Complex Selector
Use ComplexSelector when a selection needs richer custom content than a Selector option row. It is intentionally one component: ComplexSelector owns the field, trigger, popover, focus restore, and changeAction flow, while the content render prop owns the selector-specific accessible structure.
使用建议
- Use variant="secondary" beside secondary buttons, or variant="ghost" beside ghost buttons; add a startIcon when it helps identify the trigger. Use alignment="end" when a wide surface should align its end edge to the trigger.
- For staged editors, keep draft state in the composed content and call the provided onChange helper only from Apply. Cancel or dismiss without committing.
- Compose the dialog content from the appropriate accessible structure for the job: RadioList for a simple choice, Calendar/date inputs for date picking, TreeList or a searchable list for hierarchy, or a custom grid when two-dimensional arrow navigation is useful.
- Use the provided onChange helper from children; it already calls both onChange and changeAction and updates optimistic busy state.
- Call close() from custom content when a selection should dismiss the popup. Keep it open for multi-step content or freeform entry flows.
- Use Astryx focus hooks for custom content: useGridFocus for two-dimensional grids, useTreeFocus through TreeList for hierarchies, and useListFocus for custom linear collections.
- Evaluate custom content against WCAG 2.2: keyboard operation, focus visible/not obscured, names and roles, labels/instructions, target size, and contrast/non-text contrast are especially relevant for selector popovers.
- 避免:Do not rebuild trigger ARIA, popover focus management, or changeAction handling in product code.
- 避免:Do not use ComplexSelector for a plain single-column text list; use Selector instead.
主题 API
{
"targets": [
{
"className": "astryx-complex-selector",
"visualProps": [
"variant",
"size",
"status"
]
},
{
"className": "astryx-complex-selector-indicator-icon",
"states": [
"state"
]
},
{
"className": "astryx-complex-selector-popup"
}
]
}
Complex Selector
An input or toolbar trigger and dialog-popover shell for custom selector content.
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| label * | string | Label text for accessibility and the field label. | |
| value * | Value | Current controlled value. | |
| onChange | (value: Value) => void | Called when custom content commits a new value. | |
| changeAction | (value: Value) => void | Promise<void> | Async action after onChange. ComplexSelector exposes optimistic value and busy state while pending. | |
| children * | (value: Value, onChange: (value: Value) => void, close: () => void, state: ComplexSelectorRenderState) => ReactNode | Custom dialog content. Receives positional value, onChange, close, and state helpers. | |
| triggerLabel | ReactNode | Label/content shown in the closed trigger. | |
| placeholder | ReactNode | 'Select...' | Placeholder shown when triggerLabel is omitted. |
| isDisabled | boolean | Disables the selector. | |
| isLoading | boolean | Shows loading state on the trigger. | |
| status | {type: 'warning' | 'error' | 'success', message?: string} | Validation status. | |
| statusVariant | 'attached' | 'detached' | 'tooltip' | 'attached' for input selectors; 'detached' for secondary and ghost selectors | How the status message is placed relative to the input. attached overlaps directly below the input (bordered treatment; detached when variant is secondary or ghost); detached floats below as a separate element with spacing; tooltip reveals the message in an info-tip button. |
| size | 'sm' | 'md' | 'lg' | 'md' | Exact trigger height: sm 28px, md 32px, or lg 36px. |
| variant | 'input' | 'secondary' | 'ghost' | 'input' | Visual trigger style. Input is the bordered form treatment; secondary matches the secondary buttons of the active theme (borderless and neutral-filled by default); ghost is borderless and transparent to match ghost buttons. |
| startIcon | ReactNode | IconType | Icon displayed at the start of the trigger. | |
| width | SizeValue | Width of the field. | |
| placement | 'above' | 'below' | 'start' | 'end' | 'below' | Popup placement. |
| alignment | 'start' | 'center' | 'end' | 'start' | Popup alignment along the placement axis. |
| renderTrigger | (props: ComplexSelectorRenderTriggerProps) => ReactNode | Render the control the popup hangs off — a glyph in a list row, a chip, an icon button — instead of the selector's own field and button. Spread the given props ({ref, id, onClick, onKeyDown, aria-haspopup, aria-expanded, aria-controls, aria-busy}) onto it; the popup is anchored to that control and still labelled by label. The field chrome is not rendered. Pair with handleRef to open from a keystroke elsewhere. | |
| handleRef | React.Ref<ComplexSelectorHandle> | Imperative handle for programmatic control. Exposes open(), close(), toggle(), and isOpen(). | |
| onOpenChange | (isOpen: boolean) => void | Called whenever the surface opens or closes, however it happened — trigger, keyboard, light dismiss, Escape, close(), or the imperative handle. | |
| contentXstyle | StyleXStyles | StyleX styles for the popup content container. | |
| onClick | (event: MouseEvent) => void | Click handler on the trigger. Runs before the popup opens; call preventDefault() to keep it closed. A press elsewhere in the control box goes through the trigger too. With renderTrigger it runs after the built-in toggle and cannot cancel it. | |
| onKeyDown | (event: KeyboardEvent) => void | Key handler on the trigger. Runs after the built-in handling and receives every key, including the ArrowDown that opens the popup. With renderTrigger it is skipped for keys the built-in handling cancels. | |
| containerXstyle | StyleXStyles | StyleX styles for the field container (label, control, and status together). No effect inside InputGroup (style the InputGroup instead) or in a horizontal-labels FormLayout. Root xstyle, className, and style are not accepted. | |
| containerClassName | string | Class name for the field container. Same targeting as containerXstyle. | |
| containerStyle | CSSProperties | Inline styles for the field container. Same targeting as containerXstyle. | |
| controlXstyle | StyleXStyles | StyleX styles for the visual control box: the element that paints the border and background and holds the value, icons, and end controls. Never the trigger button itself. | |
| controlClassName | string | Class name for the visual control box. | |
| controlStyle | CSSProperties | Inline styles for the visual control box. |
组件结构
- Field: Field shell that provides the label and optional supporting field content.
- Trigger: Control that displays the current value or placeholder and opens the popup.
- Icon-rendered start icon: Optional leading semantic icon or icon component rendered through Icon.
- Caller-rendered start content: Optional arbitrary React content rendered directly at the start of the trigger.
- Indicator icon: Trailing chevron that rotates to reflect whether the popup is open.
- Popup: Mounted dialog surface that is painted and shown while open and hidden while closed.
示例的 StyleX 编译要求
ComplexSelectorShowcase 包含 stylex.create,需要配置 StyleX 源码编译流程。仅引入 Core 预编译 CSS 不足以运行这些示例样式,复制前请阅读样式与 Tailwind。