JGuang

复杂选择器

ComplexSelector · Astryx

交互预览
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0

复杂选择器 · 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 *stringLabel text for accessibility and the field label.
value *ValueCurrent controlled value.
onChange(value: Value) => voidCalled 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) => ReactNodeCustom dialog content. Receives positional value, onChange, close, and state helpers.
triggerLabelReactNodeLabel/content shown in the closed trigger.
placeholderReactNode'Select...'Placeholder shown when triggerLabel is omitted.
isDisabledbooleanDisables the selector.
isLoadingbooleanShows 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 selectorsHow 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.
startIconReactNode | IconTypeIcon displayed at the start of the trigger.
widthSizeValueWidth 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) => ReactNodeRender 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.
handleRefReact.Ref<ComplexSelectorHandle>Imperative handle for programmatic control. Exposes open(), close(), toggle(), and isOpen().
onOpenChange(isOpen: boolean) => voidCalled whenever the surface opens or closes, however it happened — trigger, keyboard, light dismiss, Escape, close(), or the imperative handle.
contentXstyleStyleXStylesStyleX styles for the popup content container.
onClick(event: MouseEvent) => voidClick 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) => voidKey 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.
containerXstyleStyleXStylesStyleX 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.
containerClassNamestringClass name for the field container. Same targeting as containerXstyle.
containerStyleCSSPropertiesInline styles for the field container. Same targeting as containerXstyle.
controlXstyleStyleXStylesStyleX 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.
controlClassNamestringClass name for the visual control box.
controlStyleCSSPropertiesInline 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。