Skip to content

Primitives

The @omega-flow/editor package provides reusable form field components for building custom node detail panels.

Overview

All primitives are designed to work within node detail components and follow consistent styling patterns.

tsx
import {
  Field,
  TextField,
  NumberField,
  SelectField,
  CheckboxField,
  TextAreaField,
  DurationField,
  JsonField,
  FieldGroup,
} from "@omega-flow/editor";

Field

Base wrapper component for form fields.

Props

typescript
interface FieldProps {
  label: string;
  children: React.ReactNode;
  error?: string;
  hint?: string;
}
PropTypeDescription
labelstringField label
childrenReactNodeField input
errorstringError message (red)
hintstringHint text (gray, hidden when error shown)

Usage

Use Field when building custom input components:

tsx
import { Field } from "@omega-flow/editor";

function CustomField({ label, value, onChange }) {
  return (
    <Field label={label} hint="Custom input">
      <input
        type="color"
        value={value}
        onChange={(e) => onChange(e.target.value)}
      />
    </Field>
  );
}

TextField

Single-line text input.

Props

typescript
interface TextFieldProps {
  label: string;
  value: string;
  onChange: (value: string) => void;
  placeholder?: string;
  disabled?: boolean;
  error?: string;
  hint?: string;
}
PropTypeDescription
labelstringField label
valuestringCurrent value
onChange(value: string) => voidChange handler
placeholderstringPlaceholder text
disabledbooleanDisable input
errorstringError message
hintstringHint text

Usage

tsx
import { TextField } from "@omega-flow/editor";

function MyNodeDetail({ node, onChange }) {
  const data = node.data;

  return (
    <TextField
      label="Event Type"
      value={data.event || ""}
      onChange={(value) => onChange({ ...data, event: value })}
      placeholder="e.g., user.signup"
      hint="The event that triggers this node"
    />
  );
}

NumberField

Numeric input with min/max/step validation.

Props

typescript
interface NumberFieldProps {
  label: string;
  value: number;
  onChange: (value: number) => void;
  min?: number;
  max?: number;
  step?: number;
  disabled?: boolean;
  error?: string;
  hint?: string;
}
PropTypeDescription
labelstringField label
valuenumberCurrent value
onChange(value: number) => voidChange handler
minnumberMinimum value
maxnumberMaximum value
stepnumberStep increment
disabledbooleanDisable input
errorstringError message
hintstringHint text

Usage

tsx
import { NumberField } from "@omega-flow/editor";

function MyNodeDetail({ node, onChange }) {
  const data = node.data;

  return (
    <NumberField
      label="Retry Count"
      value={data.retries || 3}
      onChange={(value) => onChange({ ...data, retries: value })}
      min={0}
      max={10}
      step={1}
      hint="Number of retry attempts"
    />
  );
}

SelectField

Dropdown select with options.

Props

typescript
interface SelectOption {
  value: string;
  label: string;
}

interface SelectFieldProps {
  label: string;
  value: string;
  options: SelectOption[];
  onChange: (value: string) => void;
  placeholder?: string;
  disabled?: boolean;
  error?: string;
  hint?: string;
}
PropTypeDescription
labelstringField label
valuestringSelected value
optionsSelectOption[]Available options
onChange(value: string) => voidChange handler
placeholderstringPlaceholder text
disabledbooleanDisable select
errorstringError message
hintstringHint text

Usage

tsx
import { SelectField } from "@omega-flow/editor";

const priorityOptions = [
  { value: "low", label: "Low" },
  { value: "medium", label: "Medium" },
  { value: "high", label: "High" },
];

function MyNodeDetail({ node, onChange }) {
  const data = node.data;

  return (
    <SelectField
      label="Priority"
      value={data.priority || "medium"}
      options={priorityOptions}
      onChange={(value) => onChange({ ...data, priority: value })}
      hint="Task priority level"
    />
  );
}

CheckboxField

Boolean toggle checkbox.

Props

typescript
interface CheckboxFieldProps {
  label: string;
  checked: boolean;
  onChange: (checked: boolean) => void;
  disabled?: boolean;
}
PropTypeDescription
labelstringField label (clickable)
checkedbooleanCurrent state
onChange(checked: boolean) => voidChange handler
disabledbooleanDisable checkbox

Usage

tsx
import { CheckboxField } from "@omega-flow/editor";

function MyNodeDetail({ node, onChange }) {
  const data = node.data;

  return (
    <CheckboxField
      label="Send notification"
      checked={data.notify || false}
      onChange={(checked) => onChange({ ...data, notify: checked })}
    />
  );
}

TextAreaField

Multi-line text input.

Props

typescript
interface TextAreaFieldProps {
  label: string;
  value: string;
  onChange: (value: string) => void;
  rows?: number;
  placeholder?: string;
  disabled?: boolean;
  error?: string;
  hint?: string;
}
PropTypeDefaultDescription
labelstring-Field label
valuestring-Current value
onChange(value: string) => void-Change handler
rowsnumber4Number of visible rows
placeholderstring-Placeholder text
disabledboolean-Disable input
errorstring-Error message
hintstring-Hint text

Usage

tsx
import { TextAreaField } from "@omega-flow/editor";

function MyNodeDetail({ node, onChange }) {
  const data = node.data;

  return (
    <TextAreaField
      label="Description"
      value={data.description || ""}
      onChange={(value) => onChange({ ...data, description: value })}
      rows={4}
      placeholder="Enter a description..."
    />
  );
}

DurationField

Duration input with unit selection (ms, s, min, h).

Props

typescript
interface DurationFieldProps {
  label: string;
  value: number;
  onChange: (value: number) => void;
  disabled?: boolean;
  error?: string;
  hint?: string;
}
PropTypeDescription
labelstringField label
valuenumberDuration in milliseconds
onChange(value: number) => voidChange handler (ms)
disabledbooleanDisable input
errorstringError message
hintstringHint text

Available Units

UnitDisplayMultiplier
msmilliseconds1
sseconds1,000
minminutes60,000
hhours3,600,000

Usage

tsx
import { DurationField } from "@omega-flow/editor";

function MyNodeDetail({ node, onChange }) {
  const data = node.data;

  return (
    <DurationField
      label="Timeout"
      value={data.params?.duration || 60000}
      onChange={(value) =>
        onChange({
          ...data,
          params: { ...data.params, duration: value },
        })
      }
      hint="How long to wait before timing out"
    />
  );
}

The component automatically:

  • Converts stored milliseconds to the most appropriate unit
  • Updates the value in milliseconds when user changes input
  • Shows "1min" for 60000ms, "2h" for 7200000ms, etc.

JsonField

JSON editor with syntax validation.

Props

typescript
interface JsonFieldProps {
  label: string;
  value: unknown;
  onChange: (value: unknown) => void;
  rows?: number;
  disabled?: boolean;
  error?: string;
  hint?: string;
}
PropTypeDefaultDescription
labelstring-Field label
valueunknown-JSON value
onChange(value: unknown) => void-Change handler
rowsnumber6Number of visible rows
disabledboolean-Disable input
errorstring-Error message
hintstring-Hint text

Features

  • Textarea with monospace font
  • Auto-formats JSON with 2-space indentation
  • Real-time JSON validation
  • Shows "Invalid JSON" error on parse failure
  • Syncs with external value changes

Usage

tsx
import { JsonField } from "@omega-flow/editor";

function MyNodeDetail({ node, onChange }) {
  const data = node.data;

  return (
    <JsonField
      label="Conditions"
      value={data.conditions || { all: [] }}
      onChange={(value) => onChange({ ...data, conditions: value })}
      rows={8}
      hint="JSON Rules Engine conditions"
    />
  );
}

Example Value

json
{
  "all": [
    {
      "fact": "user.age",
      "operator": "greaterThan",
      "value": 18
    }
  ]
}

FieldGroup

Groups related fields with optional collapse support.

Props

typescript
interface FieldGroupProps {
  label?: string;
  children: React.ReactNode;
  collapsible?: boolean;
  defaultCollapsed?: boolean;
}
PropTypeDefaultDescription
labelstring-Group header label
childrenReactNode-Child fields
collapsiblebooleanfalseEnable collapse toggle
defaultCollapsedbooleanfalseInitial collapsed state

Usage

tsx
import { FieldGroup, TextField, NumberField } from "@omega-flow/editor";

function MyNodeDetail({ node, onChange }) {
  const data = node.data;

  return (
    <div>
      <TextField
        label="Action"
        value={data.action || ""}
        onChange={(value) => onChange({ ...data, action: value })}
      />

      <FieldGroup label="Advanced Options" collapsible defaultCollapsed>
        <NumberField
          label="Timeout"
          value={data.timeout || 30}
          onChange={(value) => onChange({ ...data, timeout: value })}
        />
        <NumberField
          label="Retries"
          value={data.retries || 3}
          onChange={(value) => onChange({ ...data, retries: value })}
        />
      </FieldGroup>
    </div>
  );
}

The collapsible group shows an animated arrow indicator and hides/shows its children when clicked.


Complete Example

Here's a node detail component using multiple primitives:

tsx
import {
  TextField,
  NumberField,
  SelectField,
  CheckboxField,
  TextAreaField,
  DurationField,
  JsonField,
  FieldGroup,
  type NodeDetailProps,
} from "@omega-flow/editor";

interface MyNodeData {
  name: string;
  action: string;
  priority: string;
  timeout: number;
  retries: number;
  notify: boolean;
  description: string;
  params: Record<string, unknown>;
}

function MyNodeDetail({ node, onChange }: NodeDetailProps) {
  const data = node.data as MyNodeData;

  return (
    <div>
      <TextField
        label="Name"
        value={data.name || ""}
        onChange={(value) => onChange({ ...data, name: value })}
        placeholder="Node name"
      />

      <SelectField
        label="Action"
        value={data.action || ""}
        options={[
          { value: "send_email", label: "Send Email" },
          { value: "send_sms", label: "Send SMS" },
          { value: "webhook", label: "Call Webhook" },
        ]}
        onChange={(value) => onChange({ ...data, action: value })}
      />

      <SelectField
        label="Priority"
        value={data.priority || "medium"}
        options={[
          { value: "low", label: "Low" },
          { value: "medium", label: "Medium" },
          { value: "high", label: "High" },
        ]}
        onChange={(value) => onChange({ ...data, priority: value })}
      />

      <TextAreaField
        label="Description"
        value={data.description || ""}
        onChange={(value) => onChange({ ...data, description: value })}
        rows={3}
      />

      <FieldGroup label="Execution Settings" collapsible>
        <DurationField
          label="Timeout"
          value={data.timeout || 30000}
          onChange={(value) => onChange({ ...data, timeout: value })}
        />

        <NumberField
          label="Retries"
          value={data.retries || 3}
          onChange={(value) => onChange({ ...data, retries: value })}
          min={0}
          max={10}
        />

        <CheckboxField
          label="Send notification on failure"
          checked={data.notify || false}
          onChange={(checked) => onChange({ ...data, notify: checked })}
        />
      </FieldGroup>

      <FieldGroup label="Parameters" collapsible defaultCollapsed>
        <JsonField
          label="Custom Parameters"
          value={data.params || {}}
          onChange={(value) => onChange({ ...data, params: value })}
          hint="Additional parameters as JSON"
        />
      </FieldGroup>
    </div>
  );
}

Styling

All primitives use consistent inline styles:

  • Gray borders that turn blue on focus
  • Red borders and text for errors
  • System font with appropriate sizing
  • Proper spacing and padding

To customize styling, you can:

  1. Wrap primitives in styled containers
  2. Use CSS classes on parent elements
  3. Build custom fields using the Field base component