Editor inputs
Components in apps/frontend/src/app/(dashboard)/editor/[projectId]/[pageId]/Components/Inputs/. All are controlled by a string value unless noted. Inputs that support drags call onChange(value, live) with live: true during the drag and once with live: false (or undefined) at the end; pass both through to the writer.
Inputs resync local state from props with the render-time “adjust state on prop change” pattern rather than effects.
Shared modules
| Export | File | Meaning |
|---|---|---|
INPUT_HEIGHT, fieldStyle, selectStyle, toggleBtnStyle(active), iconBtnStyle, popoverStyle, captionStyle | inputStyles.ts | Shared inline styles on theme tokens. |
WARNING_COLOR | inputStyles.ts | var(--color-warning, #d4a017); the theme has no warning token yet. |
useScrub({ getValue, onScrub, step?, min?, max? }) | useScrub.ts | Returns { onPointerDown, onKeyDown, dragging }. Drag: 2px per step; Shift ×10, Alt ÷10; onScrub(n, true) per change and onScrub(last, false) on release if changed. onKeyDown handles ↑/↓ with the same modifiers. |
LENGTH_UNITS, TIME_UNITS, ANGLE_UNITS | MeasureInput.tsx | Common unit lists. |
Value inputs
MeasureInput
| Prop | Type | Default |
|---|---|---|
value | string | — |
onChange | (value, live?) => void | — |
units | readonly string[] | LENGTH_UNITS + auto |
step | number | 1 |
min, max | number | — |
placeholder | string | — |
Entries of units found in the internal numeric-unit table ("" unitless, lengths, fr, s/ms, angles, container units) take a number; anything else is a keyword. Values containing parentheses are expressions (unit select shows fx) and are emitted on Enter/blur only. Plain numbers are emitted per keystroke; blur doesn’t re-emit an unchanged value. Bare non-zero numbers get the first numeric unit unless "" is allowed.
NumberInput
value, onChange(value, live?), step = 1, min, max, integer = false, keywords: string[] = [], placeholder. Commits on Enter/blur; invalid text restores the last value; keywords are offered via a datalist (useId).
SliderInput
value, onChange(value, live?), min = 0, max = 1, step = 0.01, defaultValue (slider position when empty), suffix = "" (appended on emit).
SelectInput
value, onChange(value), options: (string | { value; label })[], allowCustom = false, placeholder = "—". The empty option emits "". With allowCustom, unknown values render as a text field with a preset ▾ select.
SegmentedInput
value, onChange(value), options: { value; label: ReactNode; title? }[], multiple = false, noneValue = "". Single mode toggles to "" when the active option is clicked. Multiple mode toggles space-separated tokens, keeps tokens not in options, orders known tokens by option order, and emits noneValue when empty.
TextInput
value, onChange(value), placeholder, suggestions, multiline = false. Uncontrolled (defaultValue + key={value} remount). Commits trimmed text on Enter / blur (Ctrl/Cmd+Enter for multiline) only when it differs; Escape restores.
BooleanInput
value: boolean, onChange(boolean), label?. A role="switch" button.
Pickers and editors
ColorInput
value, onChange(value, live?), variables: readonly string[] = []. Parses with parseCssColor; keeps HSV + alpha state; format (hex/rgb/hsl) from detectColorFormat. var(...) values render the variable name on the swatch and don’t move the picker. Text field accepts any color and emits it verbatim. Swatches persist to localStorage["handrix:color-swatches"] (max 16, wrapped in try/catch) and are loaded when the popover opens. Eyedropper uses window.EyeDropper when present.
SidesInput
values: Record<Side, string>, onChange(partial: Partial<Record<Side, string>>, live?), units?, label?. Side = "top" | "right" | "bottom" | "left", SIDES. Linked mode emits all four sides.
CornersInput
values: Record<Corner, string>, onChange(partial, live?). Corner = "top-left" | "top-right" | "bottom-right" | "bottom-left", CORNERS. Elliptical values are "h v"; equal parts collapse to one.
PositionInput
value, onChange(value, live?). Normalizes top left to x-first. Emits "x y", filling a missing axis with center; empty when both are empty.
LayerList<T>
| Prop | Type |
|---|---|
items | T[] |
onChange | (items: T[], live?) => void |
createItem | () => T |
summary | (item, index) => ReactNode |
renderEditor | (item, update: (patch: Partial<T>, live?) => void, index) => ReactNode |
addLabel, emptyLabel | string |
One layer expanded at a time; reorder with ↑/↓.
GradientInput
value, onChange(value, live?), variables?. Exports parseGradient(value): Gradient | null, buildGradient(g), DEFAULT_GRADIENT, type Gradient (type, repeating, angle, shape, at, stops: { color; pos }[]). to <side> directions become angles; stops without a position are spread evenly; stops are sorted on build.
EasingInput
value, onChange(value, live?). Keyword curves map to their bezier points for drawing; handle drags emit cubic-bezier(...) with y clamped to −0.5…1.5. steps(n, position) with end, start, jump-none, jump-both.
ClipPathInput
value, onChange(value, live?). Visual editing only for percentage-based polygon, circle, ellipse, inset (with round kept); otherwise only the raw text field. Polygon: click adds a point, Alt-click removes (min 3).
GridAreasInput
value, onChange(value). Parses quoted rows, pads to the widest row with .; emits "" only for an empty grid.
KeyValueListInput
| Prop | Type | Meaning |
|---|---|---|
entries | [name, value][] | |
onSet(name, value), onRemove(name) | ||
onRename?(from, to) | Renders the name as an editable field. | |
prefix | string | Shown stripped, added to new names ("data-"). |
validateName?(name) | string | null | Error message for a full name. Duplicates are rejected separately. |
emptyLabel | string |
IdRefsInput
value, onChange(value), options: IdOption[] ({ id; label }), multiple = false, placeholder. Single mode: TextInput with id suggestions and a “No element with this id” warning. Multiple: chips (dashed when the id is unknown) plus an + add id… select.
Related
- Reference: Style tools framework
- Reference: Props tools framework