# ComplexSelector

- Page: https://jiguang132.com/resources/astryx/docs/complex-selector
- Resource: Astryx (https://jiguang132.com/resources/astryx.md)
- All guides: https://jiguang132.com/resources/astryx/docs.md

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.

## Installation

```bash
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
```

Requires React 19+, base CSS and a Theme provider. See [Setup](/resources/astryx/docs/getting-started).

## 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.

### Usage guidance

- 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.
- Avoid: Do not rebuild trigger ARIA, popover focus management, or changeAction handling in product code.
- Avoid: Do not use ComplexSelector for a plain single-column text list; use Selector instead.

### Theming API

```json
{
  "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.

| Prop | Type | Default | Description |
|---|---|---|---|
| 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. |

### Anatomy

- **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.


## Examples

- Complex Selector: A two-axis picker: choose a fruit and a ripeness level from one control. ComplexSelector owns the trigger, popover, and focus restore while the custom grid owns its keyboard semantics.
- Complex Selector — Deadline Picker: A multi-step deadline field: pick a preset like Today or Next week, or switch to a custom date and time before applying. The popup stays open until the user commits, so the content owns the Apply action.
- Complex Selector — Tree Search: A destination picker that combines a search field with a TreeList hierarchy. TreeList owns tree keyboard navigation; ComplexSelector owns the trigger, popover, and focus restore. Selecting a folder closes the popup.

## Source StyleX compilation

ComplexSelectorShowcase contain stylex.create and require a StyleX source build pipeline. Precompiled Core CSS alone does not compile these examples. See [Styling](/resources/astryx/docs/styling) before copying them.

---

Index of all JGuang resources: https://jiguang132.com/llms.txt
