Appearance
State Preview System
Overview
The state preview system allows users to preview different component states (hover, focus, disabled, loading, success, error, etc.) in the configuration builder without affecting runtime behavior. This is achieved through the isStatePreview flag that separates "preview mode" from "runtime mode".
Applies to:
- Submit buttons (loading, success, error, disabled states)
- Form inputs (hover, focus, disabled states)
- Can be extended to other components
Data Flow at a Glance
User clicks state button in UI
↓
StateSection.vue (handleStateClick)
↓
Emits: update:state + update:isStatePreview
↓
FormSubmitButtonConfiguration.vue (@update:isStatePreview)
↓
updateValue('isStatePreview', true/false)
↓
Vuex Store (blockData.isStatePreview)
↓
Creative-Engine (FormSubmitButton.vue)
↓
htmlDisabled & .disabled class logic
↓
Button renders with preview colors (not disabled styling)Key Files & Their Roles
| File | Role | Key Logic |
|---|---|---|
| StateSection.vue | UI for state selection | Emits update:isStatePreview when state clicked |
| FormSubmitButtonConfiguration.vue | Configuration handler | Listens to @update:isStatePreview and calls updateValue() |
| defaults.ts | Default values | isStatePreview: false |
| types.ts | Type definitions | isStatePreview?: boolean in FormSubmitButtonProperties |
| form.ts (CE) | Runtime types | isStatePreview?: boolean in SubmitButtonBlock |
| FormSubmitButton.vue (CE) | Button renderer | Uses isStatePreview to control .disabled class and htmlDisabled |
| CreativeFormBlock.vue (CE) | CSS styling | .disabled class styling that gets skipped when isStatePreview === true |
How It Works
1. User Selects a State
vue
<!-- StateSection.vue -->
<OptionButton @click="handleStateClick(option)">
{{ option }}
</OptionButton>
handleStateClick(state) {
this.$emit('update:state', state)
this.$emit('update:isStatePreview', true) // ← ALL states are previews
}2. Configuration Receives & Stores
vue
<!-- FormSubmitButtonConfiguration.vue -->
<StateSection
@update:isStatePreview="updateValue($event, 'isStatePreview')"
/>This calls the updateValue() method from configurationLogic mixin, which updates the Vuex store.
3. Engine Receives the Flag
The blockData (including isStatePreview) flows to Creative-Engine through props:
typescript
// FormSubmitButton.vue receives props.block with isStatePreview
const props = defineProps<{
block: SubmitButtonBlock // Contains isStatePreview
}>()4. Button Rendering Logic
typescript
// Determine if .disabled class should be applied
disabled: isButtonDisabled && !props.block.isStatePreview && props.block.submitState === 'default'
// Determine if HTML disabled attribute should be set
const htmlDisabled = computed(() => {
if (props.block.isStatePreview || props.block.submitState !== 'default') {
return false // ← Don't disable HTML element in preview
}
return isButtonDisabled.value
})Result: When isStatePreview === true:
.disabledCSS class is NOT applied → colors show correctly- HTML
disabledattribute is NOT set → button is interactive - User sees the preview colors even with required fields
5. Cleanup on Navigation
typescript
// configurationLogic.ts (mixin)
beforeDestroy() {
if (this.blockData?.submitState) {
this.updateValue('default', 'submitState')
this.updateValue(false, 'isStatePreview') // ← Reset flag
}
}When user navigates away from submit button config:
submitStateresets to'default'isStatePreviewresets tofalse- Button shows runtime disabled state (if required fields present)
Why This Approach?
Advantages of isStatePreview Flag
- Explicit Intent - Code clearly states "we are in preview mode"
- Scalable - Same pattern works for all component states (hover, active, focus, etc.)
- Separates Concerns - Flag handles "is this preview?" independently from "what state is shown?"
- Debuggable - Easy to see in DevTools:
isStatePreview: true/false - Future-Proof - Can be reused for previewing other components
Alternative (Not Used)
Could have used submitState !== 'default' to determine preview mode, but:
- ❌ Mixes "what state" with "is this preview"
- ❌ Doesn't scale to other states (hover, active, etc.)
- ❌ Harder to maintain and understand
State Transitions
Runtime (isStatePreview = false)
↓
User opens submit button config
↓
User clicks state button
↓
Preview Mode (isStatePreview = true)
↓
User sees preview colors (disabled styling bypassed)
↓
User navigates to different block
↓
beforeDestroy() fires
↓
Runtime (isStatePreview = false)
↓
Button shows actual disabled state (if required fields present)Form Input State System
How It Works
Form inputs use a dual system for state styling:
Preview Mode (
.state-hover,.state-focus,.state-disabled)- Applied when
isStatePreview: trueand user selects a state - Shows preview colors from configuration
- Allows user to see and edit state styles
- Applied when
Runtime Mode (
:hover,:focus,:disabled)- Applied during actual form usage
- Uses same values from
globalInputStyling.hoverStyle,focusStyle,disabledStyle - Ensures preview matches runtime behavior
Implementation
Creative-Engine (CreativeFormBlock.vue)
typescript
getInputPseudoClassStyles() {
const globalStyling = formProps.globalInputStyling || {}
// Build styles from hoverStyle, focusStyle, disabledStyle
return {
hover: buildStateStyles(globalStyling.hoverStyle),
focus: buildStateStyles(globalStyling.focusStyle),
disabled: buildStateStyles(globalStyling.disabledStyle),
}
}
// In styles() computed property:
[`.form-input-field:hover`]: pseudoClassStyles.hover || fallback,
[`.form-input-field:focus`]: pseudoClassStyles.focus || fallback,
[`.form-input-field:disabled`]: pseudoClassStyles.disabled || fallback,Application-Frontend (FormConfiguration.vue)
State configuration is stored in:
json
{
"globalInputStyling": {
"hoverStyle": {
"backgroundSettings": { "color": "#8AB0FF" },
"borderStyle": { "borderColor": "#CBD5E0" },
"boxShadowSettings": { "blur": "12px", "color": "#0000002E" }
},
"focusStyle": { ... },
"disabledStyle": { ... }
}
}Key Difference from Submit Button
| Aspect | Submit Button | Form Inputs |
|---|---|---|
| States | Custom (loading, success, error) | Standard pseudo-classes (hover, focus, disabled) |
| Preview Classes | .state-loading, .state-success | .state-hover, .state-focus |
| Runtime Styles | :hover (generic), .disabled (custom) | :hover, :focus, :disabled (all from config) |
| Purpose | Show submission states | Show interaction states |
Why This Approach?
For Form Inputs:
- ✅ Preview styles match runtime styles exactly
- ✅ User sees what they get (WYSIWYG)
- ✅ Single source of truth for state styling
- ✅ Pseudo-classes use configuration values, not hardcoded
For Submit Button:
- ✅ Custom states (loading, success, error) for submission flow
- ✅ Standard hover/focus use generic browser behavior
- ✅ Disabled state uses custom styling from config
Testing Checklist
Submit Button
- [ ] Select "default" state →
isStatePreview: true, colors visible - [ ] Select "loading" state →
isStatePreview: true, animation visible - [ ] Select "success" state →
isStatePreview: true, checkmark visible - [ ] Select "error" state →
isStatePreview: true, error icon visible - [ ] Select "disabled" state →
isStatePreview: true, disabled colors visible - [ ] Navigate to different block →
isStatePreview: false,submitState: 'default' - [ ] Form with required fields → button shows disabled in runtime
- [ ] Form with required fields + in config → button shows preview colors
Form Inputs
- [ ] Select "hover" state →
.state-hoverclass applied with config colors - [ ] Select "focus" state →
.state-focusclass applied with config colors - [ ] Select "disabled" state →
.state-disabledclass applied with config colors - [ ] Hover over input in runtime →
:hoverpseudo-class uses same config colors - [ ] Focus input in runtime →
:focuspseudo-class uses same config colors - [ ] Disabled input in runtime →
:disabledpseudo-class uses same config colors - [ ] Change hover color in config → both preview and runtime update
- [ ] Navigate away from form →
isStatePreview: false,inputState: 'default'