Specialized components
UI framework
Specialized editor input components
17 min
overview specialized editor input components are custom ui controls designed for editing specific data types within the ir engine editor unlike standard form elements, these components provide intuitive interfaces tailored to the unique requirements of 3d editing, such as manipulating vectors, colors, and asset references by offering visual feedback and specialized interaction patterns, these components significantly improve the editing experience, allowing users to make precise adjustments efficiently core components the ir engine ui framework includes several specialized input components for different data types numericinput the numericinput component provides enhanced functionality for editing numeric values // simplified from src/components/editor/input/numeric/index tsx import react from 'react'; import { input } from '@ir engine/ui'; import { scrubber } from ' /layout/scrubber'; export const numericinput = ({ value, onchange, onrelease, smallstep = 0 1, mediumstep = 1, largestep = 10, min, max, props }) => { // handle direct input changes const handlechange = (e) => { const newvalue = parsefloat(e target value); if (!isnan(newvalue)) { onchange(clamp(newvalue, min, max)); } }; // handle scrubbing (click and drag) const handlescrub = (delta, modifiers) => { let step = smallstep; if (modifiers shift) step = largestep; else if (modifiers alt) step = mediumstep; const newvalue = value + (delta step); onchange(clamp(newvalue, min, max)); }; return ( \<div classname="flex items center"> \<scrubber onscrub={handlescrub} onscrubend={onrelease}> \<div classname="cursor ew resize px 1">⋮\</div> \</scrubber> \<input type="number" value={value} onchange={handlechange} onblur={() => onrelease? (value)} { props} /> \</div> ); }; key features numeric validation and formatting min/max value constraints "scrubbing" behavior (click and drag to adjust values) step size control with modifier keys (shift, alt) vector3input the vector3input component provides a specialized interface for editing 3d vectors // simplified from src/components/editor/input/vector3/index tsx import react from 'react'; import { numericinput } from ' /numeric'; import { vector3scrubber } from ' /vector3scrubber'; export const vector3input = ({ value, // { x, y, z } object onchange, onrelease, props }) => { // handle changes to individual axes const handleaxischange = (axis) => (newvalue) => { onchange({ value, \[axis] newvalue }); }; // handle final value commitment const handleaxisrelease = (axis) => (finalvalue) => { onrelease? ({ value, \[axis] finalvalue }); }; return ( \<div classname="flex space x 1"> \<div classname="flex 1"> \<numericinput value={value x} onchange={handleaxischange('x')} onrelease={handleaxisrelease('x')} prefix={ \<vector3scrubber axis="x" value={value x} onchange={handleaxischange('x')} onrelease={handleaxisrelease('x')} /> } { props} /> \</div> \<div classname="flex 1"> \<numericinput value={value y} onchange={handleaxischange('y')} onrelease={handleaxisrelease('y')} prefix={ \<vector3scrubber axis="y" value={value y} onchange={handleaxischange('y')} onrelease={handleaxisrelease('y')} /> } { props} /> \</div> \<div classname="flex 1"> \<numericinput value={value z} onchange={handleaxischange('z')} onrelease={handleaxisrelease('z')} prefix={ \<vector3scrubber axis="z" value={value z} onchange={handleaxischange('z')} onrelease={handleaxisrelease('z')} /> } { props} /> \</div> \</div> ); }; key features three coordinated numeric inputs (x, y, z) per axis scrubbing with color coded labels unified value object handling optional uniform scaling mode colorinput the colorinput component provides a visual interface for selecting colors // simplified from src/primitives/tailwind/color/index tsx import react, { usestate } from 'react'; import { sketch as sketchpicker } from '@uiw/react color sketch'; import { hextorgb, rgbtohex } from ' / /utils/colorconversion'; export const colorinput = ({ value, // rgb color object or hex string onchange, onrelease, props }) => { const \[ispickeropen, setispickeropen] = usestate(false); // convert internal color format to hex for display const hexcolor = typeof value === 'string' ? value rgbtohex(value); // handle color picker changes const handlechange = (color) => { const newcolor = hextorgb(color hex); onchange(newcolor); }; // handle picker close const handleclose = () => { setispickeropen(false); onrelease? (value); }; return ( \<div classname="relative"> \<div classname="w 8 h 8 rounded border border ui outline cursor pointer" style={{ backgroundcolor hexcolor }} onclick={() => setispickeropen(!ispickeropen)} /> {ispickeropen && ( \<div classname="absolute z 10 mt 1"> \<div classname="fixed inset 0" onclick={handleclose} /> \<sketchpicker color={hexcolor} onchange={handlechange} onclose={handleclose} /> \</div> )} \</div> ); }; key features color swatch preview popup color picker with rgb, hsl, and hex input alpha channel support color format conversion utilities textureinput the textureinput component provides an interface for selecting and previewing texture assets // simplified from src/components/editor/input/texture/index tsx import react, { usestate, useeffect } from 'react'; import { filebrowserinput } from ' /filebrowser'; import { assetmanager } from '@ir engine/core'; export const textureinput = ({ value, // texture path or url onchange, onrelease, showpreview = true, props }) => { const \[previewsrc, setpreviewsrc] = usestate(''); // load preview when texture path changes useeffect(() => { if (value && showpreview) { assetmanager gettexturepreview(value) then(previewurl => setpreviewsrc(previewurl)) catch(() => setpreviewsrc('')); } else { setpreviewsrc(''); } }, \[value, showpreview]); return ( \<div classname="flex flex col"> {showpreview && previewsrc && ( \<div classname="mb 2 border border ui outline rounded overflow hidden"> \<img src={previewsrc} alt="texture preview" classname="max h 24 object contain" /> \</div> )} \<filebrowserinput value={value} onchange={onchange} onrelease={onrelease} acceptfiletypes={\[' png', ' jpg', ' jpeg', ' webp']} { props} /> \</div> ); }; key features file path input with browse button texture preview thumbnail file type filtering integration with asset management system selectinput the selectinput component provides a dropdown interface for selecting from predefined options // simplified from src/components/editor/input/select/index tsx import react from 'react'; import { select } from '@ir engine/ui'; export const selectinput = ({ value, onchange, options, // array of { label, value } objects props }) => { const handlechange = (e) => { onchange(e target value); }; return ( \<select value={value} onchange={handlechange} { props} \> {options map(option => ( \<option key={option value} value={option value}> {option label} \</option> ))} \</select> ); }; key features structured options with labels and values consistent styling with other inputs support for option groups searchable variant for long option lists inputgroup container the inputgroup component provides consistent layout and labeling for input components // simplified from src/components/editor/input/group/index tsx import react from 'react'; import { label } from '@ir engine/ui'; import { tooltip } from '@ir engine/ui'; import { luinfo } from 'react icons/lu'; export const inputgroup = ({ name, label, info, children, props }) => { return ( \<div classname="mb 2" { props}> \<div classname="flex items center mb 1"> \<label htmlfor={name} classname="text xs font medium"> {label} \</label> {info && ( \<tooltip content={info}> \<div classname="ml 1 text text secondary"> \<luinfo size={12} /> \</div> \</tooltip> )} \</div> \<div> {children} \</div> \</div> ); }; key features consistent spacing and alignment label with optional tooltip for additional information accessible labeling with proper html semantics flexible container for any input component scrubbing functionality many specialized inputs implement "scrubbing" behavior, allowing users to click and drag to adjust values // simplified from src/components/editor/layout/scrubber tsx import react, { useref, usecallback } from 'react'; export const scrubber = ({ children, onscrub, onscrubend, sensitivity = 1, }) => { const isdragging = useref(false); const lastposition = useref({ x 0, y 0 }); // start scrubbing on mouse down const handlemousedown = usecallback((e) => { isdragging current = true; lastposition current = { x e clientx, y e clienty }; // capture mouse events on the entire document document addeventlistener('mousemove', handlemousemove); document addeventlistener('mouseup', handlemouseup); e preventdefault(); }, \[]); // calculate delta and call onscrub during mouse movement const handlemousemove = usecallback((e) => { if (!isdragging current) return; const deltax = (e clientx lastposition current x) sensitivity; // determine modifier keys const modifiers = { shift e shiftkey, alt e altkey, ctrl e ctrlkey }; onscrub(deltax, modifiers); lastposition current = { x e clientx, y e clienty }; }, \[onscrub, sensitivity]); // end scrubbing on mouse up const handlemouseup = usecallback(() => { if (isdragging current) { isdragging current = false; onscrubend? (); // remove document level event listeners document removeeventlistener('mousemove', handlemousemove); document removeeventlistener('mouseup', handlemouseup); } }, \[onscrubend]); return ( \<div classname="cursor ew resize" onmousedown={handlemousedown} \> {children} \</div> ); }; the scrubbing workflow follows this sequence sequencediagram participant user participant scrubber participant document participant inputcomponent participant propertypanel user >>scrubber mouse down scrubber >>document add mousemove/mouseup listeners scrubber >>scrubber store initial position user >>document mouse move document >>scrubber mousemove event scrubber >>scrubber calculate delta scrubber >>inputcomponent onscrub(delta, modifiers) inputcomponent >>propertypanel onchange(newvalue) user >>document mouse up document >>scrubber mouseup event scrubber >>document remove event listeners scrubber >>inputcomponent onscrubend() inputcomponent >>propertypanel onrelease(finalvalue) usage examples basic property editing import { inputgroup, numericinput } from '@ir engine/ui'; function lightintensityeditor({ entity }) { const lightcomponent = usecomponent(entity, lightcomponent); return ( \<inputgroup name="intensity" label="light intensity" info="controls how bright the light appears" \> \<numericinput value={lightcomponent intensity value} onchange={updateproperty(lightcomponent, 'intensity')} onrelease={commitproperty(lightcomponent, 'intensity')} min={0} max={10} smallstep={0 1} /> \</inputgroup> ); } vector property editing import { inputgroup, vector3input } from '@ir engine/ui'; function positioneditor({ entity }) { const transformcomponent = usecomponent(entity, transformcomponent); return ( \<inputgroup name="position" label="position" \> \<vector3input value={transformcomponent position value} onchange={updateproperty(transformcomponent, 'position')} onrelease={commitproperty(transformcomponent, 'position')} /> \</inputgroup> ); } material property editing import { inputgroup, colorinput, textureinput, selectinput } from '@ir engine/ui'; function materialeditor({ entity }) { const materialcomponent = usecomponent(entity, materialcomponent); const blendmodes = \[ { label 'normal', value 'normal' }, { label 'additive', value 'additive' }, { label 'multiply', value 'multiply' } ]; return ( <> \<inputgroup name="basecolor" label="base color"> \<colorinput value={materialcomponent color value} onchange={updateproperty(materialcomponent, 'color')} onrelease={commitproperty(materialcomponent, 'color')} /> \</inputgroup> \<inputgroup name="albedomap" label="albedo texture"> \<textureinput value={materialcomponent albedomap value} onchange={updateproperty(materialcomponent, 'albedomap')} onrelease={commitproperty(materialcomponent, 'albedomap')} /> \</inputgroup> \<inputgroup name="blendmode" label="blend mode"> \<selectinput value={materialcomponent blendmode value} options={blendmodes} onchange={updateproperty(materialcomponent, 'blendmode')} /> \</inputgroup> \</> ); } next steps with an understanding of the specialized input components that enable intuitive property editing, the next chapter explores the icon system that provides visual cues throughout the ui next icon system docid\ ob9xwhgbxlpr7h1iji4da