ConditionEditor

GithubStorybook

The ConditionEditor components lay out editable condition rows — a card per condition holding the controls that edit it, joined to the rows around it by their operator.

ConditionEditor

Usage

  • Use ConditionEditorItem for one editable condition row, stacked in a ConditionEditor. Use ConditionViewerItem instead when the condition is read-only.
  • These components lay out rows, and a set's header/headerActions/footer around them. The controls inside those (the All/Any match, add-condition/add-group, duplicate/remove group) and drag-and-drop behavior belong to the application.
  • Put a group's match control and description in the set's header, its own duplicate/remove in headerActions, and add-condition/add-group in the set's footer. Do not render your own header or footer row beside a ConditionEditor.
  • When a set has a header that sets its match, keep the operator labels between its rows read-only (no operatorOptions); the header is the one place to change it.
  • This component is alpha: its API and visuals will change in minor releases. Do not build abstractions that depend on its exact layout.
  • Pass the first line of controls as the item's children directly. Do not wrap them in an InlineStack or Stack.
  • Put anything stacked under the first line (refinements, filter rows, an "Add filter" button) in details.
  • Put row-level actions (duplicate, remove) in actions, not among the controls.
  • Use the item's footer for one short line at the foot of the card, such as an example of what the condition matches ("e.g. Ada Lovelace’s plan is pro") or a link. Compose it yourself (Text text="product-pxs" color="secondary", Link); Pluma styles nothing in it. Do not put errors there.
  • Use heading to name what kind of condition a row is ("Object attribute"), optionally with its Icon in a sm Coin. Leave the Coin's default gray; don't color-code kinds. Derive it from the selected field; do not add a separate type Select when the field picker already covers every kind.
  • Set operator on every row after the first, as plain sentence-case text ("And", "Or"). Leave it unset on the first row, and do not pass a Badge yourself.
  • To make an operator switchable, pass operatorOptions and onOperatorChange; Pluma renders a small SegmentedControl. Do not render your own Select, menu or tag on the connector.
  • One operator for a whole set goes in the set's header (All / Any), with read-only operators between rows. Never make one row's operator switchable and have later rows follow it. Per-row controls are fine on any rows where each operator is independent; each changes its own row only, and a set that mixes operators should nest the tighter part.
  • For a group's All / Any match in its header, use a small SegmentedControl named "Group operator", not a Select: a choice of two shows both.

Appearance

  • Nest a set of conditions by putting a ConditionEditor inside an item. There is no nesting prop. Do not draw your own group border or background.
  • Do not draw your own lines between conditions; the editor joins a set of two or more.
  • On a background other than surface-content, set the connectorSurfaceColor CSS variable to that background.
  • Do not restyle items with className or inline styles. Use the public CSS variables exported from @customerio/pluma-components/css.

Behaviors

  • Every form control and Button in a row is aligned on the same center line, and errors show below their control without moving the row. Do not pass withCenterBaseline or wrap controls in CenterBaseline.
  • Wrap other inline content (icons, popover triggers) in CenterBaseline so it lines up with the controls.
  • Set withHandle to render the reorder button; isHandleDisabled disables it. It does not drag on its own: pass handleRef and give the element to a drag-and-drop library. In React, any ref works (dnd-kit's handleRef directly). In Ember, pass a callback that receives the button, or null when it's removed. Do not render a handle button yourself.
  • Show handles on every row of a set or on none.
  • In Ember, write named blocks unconditionally and drive them with hasHeadingBlock, hasDetailsBlock, hasFooterBlock and hasActionsBlock (and hasHeaderBlock, hasHeaderActionsBlock, hasFooterBlock on ConditionEditor).
  • Leave connectorPosition unset when items render as siblings. In a virtualized list, set it on every item (first/middle/last by index in the whole set, only for a set of one).

Accessibility

  • Give every control an accessible name (ariaLabel or label); a row has no visible labels. A heading names the row's controls as a group but does not replace each control's own name.
  • Give every icon-only action an aria-label that names the action ("Remove condition").
  • The handle is always named "Reorder condition". The drag-and-drop library must provide keyboard reordering and announce moves.
  • A set with a header is a group named by it; don't add role or aria-labelledby yourself. Name the header's controls ("Group operator").

On this page