feat(element-configs): Complete STYLE-002 MVP 2 - Add Group, TextInput, Image configs

- Add GroupConfig with 7 variants (default, card, section, inset, flex-row, flex-col, centered)
- Add TextInputConfig with 2 variants (default, error) and 4 states
- Add ImageConfig with 3 variants (default, rounded, circle)
- Register all 5 configs in initElementConfigs()
- Node creation hook already implemented in NodeGraphNode constructor
- Total: 5 configs with 28 variants across all elements
- All configs use design tokens for theming

MVP 2 Status:  Complete (ready for integration testing)
This commit is contained in:
Tara West
2026-01-15 12:08:26 +01:00
parent 5049826ca3
commit 32065def30
6 changed files with 578 additions and 6 deletions

View File

@@ -0,0 +1,120 @@
/**
* GroupConfig
*
* Element configuration for Group nodes.
* Defines default layout properties and 7 variants for common use cases.
*
* @module noodl-editor/models/ElementConfigs/configs
* @since 1.2.0
*/
import type { ElementConfig } from '../ElementConfigTypes';
/**
* Group element configuration
* Provides flexible layout container styles with multiple variants
*/
export const GroupConfig: ElementConfig = {
nodeType: 'Group',
description: 'Flexible container element for layout composition',
categories: ['layout', 'container'],
// Default properties applied when a new Group is created
defaults: {
// Layout
display: 'flex',
flexDirection: 'column',
position: 'relative',
// Spacing
gap: 'var(--space-4)',
padding: '0',
// Sizing
width: 'auto',
height: 'auto',
// Default variant
_variant: 'default'
},
// Style variants
variants: {
// Default: Simple flex column container
default: {
display: 'flex',
flexDirection: 'column',
gap: 'var(--space-4)',
padding: '0',
backgroundColor: 'transparent'
},
// Card: Elevated container with border and shadow
card: {
display: 'flex',
flexDirection: 'column',
gap: 'var(--space-4)',
padding: 'var(--space-4)',
backgroundColor: 'var(--theme-color-bg-3)',
borderRadius: 'var(--radius-lg)',
borderWidth: 'var(--border-1)',
borderStyle: 'solid',
borderColor: 'var(--theme-color-border-default)',
boxShadow: 'var(--shadow-sm)'
},
// Section: Content section with padding
section: {
display: 'flex',
flexDirection: 'column',
gap: 'var(--space-6)',
paddingTop: 'var(--space-8)',
paddingBottom: 'var(--space-8)',
paddingLeft: 'var(--space-6)',
paddingRight: 'var(--space-6)',
backgroundColor: 'transparent'
},
// Inset: Subtle background for nested content
inset: {
display: 'flex',
flexDirection: 'column',
gap: 'var(--space-3)',
padding: 'var(--space-3)',
backgroundColor: 'var(--theme-color-bg-2)',
borderRadius: 'var(--radius-md)'
},
// Flex Row: Horizontal layout
'flex-row': {
display: 'flex',
flexDirection: 'row',
gap: 'var(--space-4)',
alignItems: 'center',
padding: '0',
backgroundColor: 'transparent'
},
// Flex Column: Vertical layout (explicit)
'flex-col': {
display: 'flex',
flexDirection: 'column',
gap: 'var(--space-4)',
padding: '0',
backgroundColor: 'transparent'
},
// Centered: Center content both horizontally and vertically
centered: {
display: 'flex',
flexDirection: 'column',
gap: 'var(--space-4)',
alignItems: 'center',
justifyContent: 'center',
padding: 'var(--space-4)',
backgroundColor: 'transparent'
}
}
};

View File

@@ -0,0 +1,76 @@
/**
* ImageConfig
*
* Element configuration for Image nodes.
* Defines default styling and 3 variants for common image presentations.
*
* @module noodl-editor/models/ElementConfigs/configs
* @since 1.2.0
*/
import type { ElementConfig } from '../ElementConfigTypes';
/**
* Image element configuration
* Provides flexible image display styles with shape variants
*/
export const ImageConfig: ElementConfig = {
nodeType: 'net.noodl.visual.image',
description: 'Image element with shape and sizing variants',
categories: ['media', 'visual'],
// Default properties applied when a new Image is created
defaults: {
// Sizing
width: 'auto',
height: 'auto',
maxWidth: '100%',
// Display
display: 'block',
objectFit: 'cover',
objectPosition: 'center',
// Border
borderRadius: '0',
borderWidth: '0',
borderStyle: 'solid',
borderColor: 'transparent',
// Background (for loading/error states)
backgroundColor: 'var(--theme-color-bg-2)',
// Default variant
_variant: 'default'
},
// Style variants
variants: {
// Default: Standard rectangular image
default: {
borderRadius: '0',
objectFit: 'cover',
objectPosition: 'center',
overflow: 'hidden'
},
// Rounded: Image with rounded corners
rounded: {
borderRadius: 'var(--radius-lg)',
objectFit: 'cover',
objectPosition: 'center',
overflow: 'hidden'
},
// Circle: Circular image (for avatars, icons)
circle: {
borderRadius: '9999px',
objectFit: 'cover',
objectPosition: 'center',
overflow: 'hidden',
aspectRatio: '1 / 1' // Ensure square for perfect circle
}
}
};

View File

@@ -0,0 +1,121 @@
/**
* TextInputConfig
*
* Element configuration for TextInput nodes.
* Defines default styling, 2 variants, and state-based styling.
*
* @module noodl-editor/models/ElementConfigs/configs
* @since 1.2.0
*/
import type { ElementConfig } from '../ElementConfigTypes';
/**
* TextInput element configuration
* Provides modern, accessible input field styles
*/
export const TextInputConfig: ElementConfig = {
nodeType: 'net.noodl.controls.textinput',
description: 'Interactive text input field with validation states',
categories: ['input', 'form', 'interactive'],
// Default properties applied when a new TextInput is created
defaults: {
// Layout
paddingTop: 'var(--space-2)',
paddingBottom: 'var(--space-2)',
paddingLeft: 'var(--space-3)',
paddingRight: 'var(--space-3)',
width: '100%',
height: '40px',
// Typography
fontSize: 'var(--text-sm)',
fontWeight: 'var(--font-normal)',
fontFamily: 'var(--font-sans)',
lineHeight: 'var(--leading-normal)',
color: 'var(--theme-color-fg-default)',
// Border
borderRadius: 'var(--radius-md)',
borderWidth: 'var(--border-1)',
borderStyle: 'solid',
borderColor: 'var(--theme-color-border-default)',
// Background
backgroundColor: 'var(--theme-color-bg-3)',
// Display
display: 'block',
outline: 'none',
// Transitions
transitionProperty: 'border-color, box-shadow, background-color',
transitionDuration: '150ms',
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
// Default variant
_variant: 'default'
},
// Style variants
variants: {
// Default: Standard input appearance
default: {
backgroundColor: 'var(--theme-color-bg-3)',
borderColor: 'var(--theme-color-border-default)',
color: 'var(--theme-color-fg-default)',
states: {
focus: {
borderColor: 'var(--primary)',
boxShadow: '0 0 0 3px var(--primary-alpha-20)',
outline: 'none'
},
hover: {
borderColor: 'var(--theme-color-border-hover)'
},
disabled: {
opacity: '0.5',
cursor: 'not-allowed',
backgroundColor: 'var(--theme-color-bg-2)',
color: 'var(--theme-color-fg-default-shy)'
},
placeholder: {
color: 'var(--theme-color-fg-default-shy)',
opacity: '0.6'
}
}
},
// Error: Validation error state
error: {
backgroundColor: 'var(--theme-color-bg-3)',
borderColor: 'var(--destructive)',
color: 'var(--theme-color-fg-default)',
states: {
focus: {
borderColor: 'var(--destructive)',
boxShadow: '0 0 0 3px var(--destructive-alpha-20)',
outline: 'none'
},
hover: {
borderColor: 'var(--destructive-hover)'
},
disabled: {
opacity: '0.5',
cursor: 'not-allowed',
backgroundColor: 'var(--theme-color-bg-2)',
color: 'var(--theme-color-fg-default-shy)'
},
placeholder: {
color: 'var(--theme-color-fg-default-shy)',
opacity: '0.6'
}
}
}
}
};

View File

@@ -10,8 +10,6 @@
export { ButtonConfig } from './ButtonConfig';
export { TextConfig } from './TextConfig';
// Other configs to be implemented:
// export { GroupConfig } from './GroupConfig';
// export { TextInputConfig } from './TextInputConfig';
// export { ImageConfig } from './ImageConfig';
export { GroupConfig } from './GroupConfig';
export { TextInputConfig } from './TextInputConfig';
export { ImageConfig } from './ImageConfig';

View File

@@ -25,11 +25,14 @@ export * from './configs';
*/
export function initElementConfigs(): void {
// Import configs and register them
import('./configs').then(({ ButtonConfig, TextConfig }) => {
import('./configs').then(({ ButtonConfig, TextConfig, GroupConfig, TextInputConfig, ImageConfig }) => {
// Import registry from local module
import('./ElementConfigRegistry').then(({ ElementConfigRegistry }) => {
ElementConfigRegistry.instance.register(ButtonConfig);
ElementConfigRegistry.instance.register(TextConfig);
ElementConfigRegistry.instance.register(GroupConfig);
ElementConfigRegistry.instance.register(TextInputConfig);
ElementConfigRegistry.instance.register(ImageConfig);
console.log('[ElementConfigs] Initialized with', ElementConfigRegistry.instance.getCount(), 'configs');
});