4.9 KiB
STYLE-002: Element Configs & Variants - Changelog
2026-02-18 - Phase 1, 2 & 3 Complete
Phase 1: Config System Architecture
New Files
packages/noodl-editor/src/editor/src/models/ElementConfigs/
-
ElementConfigTypes.ts— TypeScript interfaces for the config system:StateStyles— hover/active/focus/disabled/placeholder style mapsVariantConfig— base styles + optionalstatesobjectSizePresets— named size override mapsElementConfig— full config for a node type (defaults + sizes + variants)ResolvedVariant— base styles + states after stripping thestateskey
-
ElementConfigRegistry.ts— Singleton registry:register(config)— add a configget(nodeType)/has(nodeType)/getAll()— lookupgetVariantNames(nodeType)— returnsstring[]of available variant namesresolveVariant(nodeType, variantName)→ResolvedVariant | undefinedapplyDefaults(node, typeName)— stamps defaults + initial variant onto a node'sparametersapplyVariant(node, typeName, variantName)— stamps a variant's base styles + updates_variantmarker- All built-in configs auto-registered on module load
-
configs/ButtonConfig.ts— Button (net.noodl.controls.button):- Variants: primary, secondary, outline, ghost, destructive, link
- Size presets: sm, md, lg, xl
- Interaction states: hover, active, disabled per variant
-
configs/GroupConfig.ts— Group (net.noodl.visual.group):- Variants: default, card, section, inset, flex-row, flex-col, centered
-
configs/TextConfig.ts— Text node:- BUG FIX: defaults now include
width: auto,flexShrink: 1,flexGrow: 0,minWidth: 0(previously: implicitwidth: 100%caused Text elements to push siblings off-screen in row layouts) - Variants: body, heading-1 through heading-6, muted, label, small, code, lead, blockquote
- BUG FIX: defaults now include
-
configs/TextInputConfig.ts— TextInput (net.noodl.controls.textinput):- Variants: default (with focus ring), error
-
configs/CheckboxConfig.ts— Checkbox (net.noodl.controls.checkbox):- Variant: default
-
configs/index.ts— barrel export -
index.ts— public module export
Phase 2: Node Creation Integration
Modified
-
views/NodePicker/NodePicker.utils.ts— AddedElementConfigRegistry.applyDefaults(node, type.name)call increateNodeFunction, immediately afterNodeGraphNode.fromJSON(...). This is the single entry point for all user-initiated node creation — no changes needed to NodeGraphModel or project loading paths.- Safe for existing projects: defaults only stamp properties not already set
- No-op for node types without a registered config
- Works for root nodes, child nodes, and auto-attach-to-root paths
Phase 3: VariantSelector UI Component
New Files
packages/noodl-core-ui/src/components/inputs/VariantSelector/
-
VariantSelector.tsx— Controlled dropdown component:- Props:
variants: string[],currentVariant: string | undefined,onVariantChange: (name) => void,disabled?,label? - Keyboard accessible (Escape to close)
- Closes on outside click
- Formats variant names for display (
heading-1→Heading 1) - Active variant shown with checkmark + primary color highlight
- Props:
-
VariantSelector.module.scss— Styled with design tokens exclusively (no hardcoded colors) -
index.ts— barrel export
What's Pending
Property Panel Integration (STYLE-004)
Wiring VariantSection into the property editor requires understanding the legacy TypeView.js / propertyeditor.ts system. The VariantSelector component is built and ready — integration is scoped to STYLE-004 which covers Property Panel UX overhaul.
State Styles (Phase 4)
StateStyles (hover, focus, active, disabled) are defined in all configs and stored in ResolvedVariant.states. Applying them at runtime requires investigation of the existing visual states system in noodl-viewer-react. Scoped to STYLE-004.
Key Discoveries
- NodePicker.utils.ts is the sole user-initiated node creation path — all
addRoot/addChildcalls with{ undo: true, label: 'create' }flow throughcreateNodeFunction. No need to touch NodeGraphModel. - NodeGraphNode.parameters is a plain object — not getter/setter based. The registry uses direct
node.parameters[key]access. - Loading vs creation differentiation: when loading from JSON,
NodeGraphNode.fromJSONpre-populates parameters beforeaddRootis called. TheapplyDefaultsguard (if param === undefined) ensures existing project nodes aren't affected. - Text node width issue: the
defaultCssintext.jsonly has{ position: 'relative', display: 'flex' }. The width issue comes from dimension port defaults. Stampingwidth: auto+ flex props viaapplyDefaultsfixes it for newly created nodes.
Started: 2026-02-18