+
+
+ rule.kind !== "unset" &&
+ onChange({ ...rule, enabled: e.target.checked })
+ }
+ />
+
+
+ {summary}
+
+ {ruleDescription(rule)}
+ {!isUnset && (
+
+ {[...new Set(ruleColors(rule))].slice(0, 3).map((color) => (
+
+ ))}
+
+ )}
+ {ruleFillLabel(rule) && (
+ {ruleFillLabel(rule)}
+ )}
+
+
+
+ {errors && (
+
+
+
+ )}
+
+ {expanded ? : }
+
+
+
+
+
+
+
+
+
+ onMove("top")}
+ icon={}
+ >
+ Move to top
+
+ onMove(-1)}
+ icon={}
+ >
+ Move up
+
+ onMove(1)}
+ icon={}
+ >
+ Move down
+
+ onMove("bottom")}
+ icon={}
+ >
+ Move to bottom
+
+
+ }
+ >
+ Remove rule
+
+
+
+
+
+
+ {expanded && (
+
+
+
+ Column
+
+ onChange(
+ rule.kind === "unset"
+ ? { ...rule, target: null }
+ : { ...rule, target: { kind: "column", name: "" } },
+ )
+ }
+ />
+ {errors?.column && {errors.column}}
+
+
+ Condition
+
+ {errors?.condition && (
+ {errors.condition}
+ )}
+
+
+ {rule.kind !== "unset" && (
+ <>
+
+
+
+ {rule.kind === "steps" && (
+
+ )}
+
+ {betweenFill && (
+
+ Fill
+
+
+ )}
+ {rule.kind !== "steps" && (
+
+
+ {betweenFill?.kind === "gradient" ? "Colors" : "Color"}
+
+
+ {betweenFill?.kind === "gradient" ? (
+ <>
+
+
+ onChange({ ...rule, color })
+ }
+ />
+ Low
+
+
+
+ High
+
+ >
+ ) : (
+ onChange({ ...rule, color })}
+ />
+ )}
+
+
+ )}
+
+ Display
+
+ onChange({
+ ...rule,
+ display: display as HighlightDisplay,
+ })
+ }
+ />
+
+
+ Applies to
+
+ onChange({
+ ...rule,
+ appliesTo: appliesTo as HighlightAppliesTo,
+ })
+ }
+ />
+
+
+ >
+ )}
+
+ )}
+
+
+ )
+}
diff --git a/src/scenes/Editor/Notebook/CellHighlight/StepsEditor.tsx b/src/scenes/Editor/Notebook/CellHighlight/StepsEditor.tsx
new file mode 100644
index 000000000..fd6ccac04
--- /dev/null
+++ b/src/scenes/Editor/Notebook/CellHighlight/StepsEditor.tsx
@@ -0,0 +1,125 @@
+import React from "react"
+import { XIcon } from "@phosphor-icons/react"
+import { Button, IconButton } from "../../../../components"
+import {
+ createRuleId,
+ DEFAULT_RULE_COLOR,
+ type HighlightStep,
+ type StepsRule,
+} from "../../../../components/ResultGrid/highlight"
+import { ColorSwatch } from "./ColorSwatch"
+import { FieldGroup, FieldLabel } from "../CellChart/chartSettingsStyles"
+import {
+ AddRow,
+ CompactInput,
+ FieldError,
+ StepActionSlot,
+ StepLabel,
+ StepLine,
+ StepRemainder,
+} from "./highlightSettingsStyles"
+import { stepErrorKey, type RuleErrors } from "./ruleValidation"
+
+type Props = {
+ rule: StepsRule
+ errors: RuleErrors
+ onChange: (rule: StepsRule) => void
+}
+
+// Steps stay in edit order; the engine sorts by bound when it evaluates.
+// The field is uncontrolled so it can be emptied while typing; the draft
+// keeps its last finite value until a new one is typed.
+const parseBound = (raw: string): number | null => {
+ if (raw.trim() === "") return null
+ const parsed = Number(raw)
+ return Number.isFinite(parsed) ? parsed : null
+}
+
+export const StepsEditor: React.FC