Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:02:33 +08:00
commit 0c40192593
82 changed files with 18699 additions and 0 deletions

View File

@@ -0,0 +1,610 @@
# Screenshot Import Helper Functions
This document contains all helper functions used during screenshot-to-uxscii conversion. These functions transform vision analysis data into valid uxscii component structures.
## Component Metadata Helpers
### mapTypeToCategory()
Maps component type to UXM category for proper organization:
```typescript
function mapTypeToCategory(componentType: string): string {
const categoryMap = {
// Input category - interactive data entry
'input': 'input', 'checkbox': 'input', 'radio': 'input',
'select': 'input', 'slider': 'input', 'toggle': 'input',
// Layout category - structural containers
'container': 'layout', 'card': 'layout', 'panel': 'layout',
'tabs': 'layout', 'fieldset': 'layout',
// Display category - content presentation
'text': 'display', 'heading': 'display', 'label': 'display',
'badge': 'display', 'icon': 'display', 'image': 'display',
'divider': 'display',
// Navigation category - movement and wayfinding
'navigation': 'navigation', 'breadcrumb': 'navigation',
'pagination': 'navigation', 'link': 'navigation',
// Feedback category - system responses
'alert': 'feedback', 'toast': 'feedback', 'progress': 'feedback',
'spinner': 'feedback',
// Utility category - action triggers
'button': 'utility', 'form': 'utility',
// Overlay category - modal displays
'modal': 'overlay',
// Data category - structured information
'list': 'data', 'table': 'data', 'tree': 'data', 'chart': 'data'
};
return categoryMap[componentType] || 'custom';
}
```
### generateComponentName()
Creates human-readable component name from kebab-case ID:
```typescript
function generateComponentName(componentId: string): string {
// Convert kebab-case to Title Case
return componentId
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
// Examples:
// "email-input" → "Email Input"
// "submit-button" → "Submit Button"
// "user-profile-card" → "User Profile Card"
```
### generateComponentDescription()
Creates description based on type, properties, and accessibility:
```typescript
function generateComponentDescription(
componentType: string,
visualProperties: any,
accessibility: any
): string {
const typeDescriptions = {
'button': 'An interactive button component for user actions',
'input': 'A text input field for user data entry',
'checkbox': 'A checkbox input for boolean selection',
'radio': 'A radio button for single selection from a group',
'select': 'A dropdown select component for choosing from options',
'card': 'A container component for grouping related content',
'modal': 'An overlay component for focused interactions',
'alert': 'A feedback component for displaying messages',
'navigation': 'A navigation component for site/app navigation',
'form': 'A form container for collecting user input',
'badge': 'A small badge component for labels or counts',
'icon': 'An icon component for visual symbols',
'text': 'A text display component',
'heading': 'A heading component for section titles',
'divider': 'A visual divider for separating content'
};
let description = typeDescriptions[componentType] || `A ${componentType} component`;
// Add context from accessibility label if available
if (accessibility?.label && accessibility.label !== visualProperties.textContent) {
description += `. ${accessibility.label}`;
}
return description;
}
```
## Behavior Helpers
### inferBackground()
Determines background fill pattern based on component type:
```typescript
function inferBackground(componentType: string): string {
const backgroundMap = {
'button': 'filled', // Buttons have solid background
'input': 'transparent', // Inputs are hollow
'card': 'filled', // Cards have background
'modal': 'filled', // Modals have background
'alert': 'filled', // Alerts have background
'badge': 'filled', // Badges have background
'panel': 'filled', // Panels have background
'toast': 'filled' // Toasts have background
};
return backgroundMap[componentType] || 'transparent';
}
```
### generateInteractions()
Generates interaction array based on component type:
```typescript
function generateInteractions(componentType: string): string[] {
const interactionMap = {
'button': ['click', 'keyboard'],
'input': ['click', 'keyboard', 'type'],
'checkbox': ['click', 'keyboard'],
'radio': ['click', 'keyboard'],
'select': ['click', 'keyboard'],
'slider': ['click', 'drag', 'keyboard'],
'toggle': ['click', 'keyboard'],
'link': ['click', 'keyboard'],
'tabs': ['click', 'keyboard'],
'navigation': ['click', 'keyboard'],
'modal': ['click', 'keyboard'], // Close on ESC
'toast': [], // No interaction (auto-dismiss)
'progress': [], // No interaction (passive display)
'spinner': [] // No interaction (passive display)
};
return interactionMap[componentType] || [];
}
```
### isFocusable()
Determines if component should be focusable for keyboard navigation:
```typescript
function isFocusable(componentType: string): boolean {
const focusableTypes = [
'button', 'input', 'checkbox', 'radio', 'select',
'slider', 'toggle', 'link', 'tabs', 'navigation'
];
return focusableTypes.includes(componentType);
}
```
### generateKeyboardSupport()
Generates keyboard shortcuts based on component type:
```typescript
function generateKeyboardSupport(componentType: string): string[] {
const keyboardMap = {
'button': ['Enter', 'Space'],
'input': ['Tab', 'Escape'],
'checkbox': ['Space'],
'radio': ['Arrow keys'],
'select': ['Arrow keys', 'Enter', 'Escape'],
'slider': ['Arrow keys', 'Home', 'End'],
'toggle': ['Space'],
'link': ['Enter'],
'tabs': ['Arrow keys', 'Home', 'End'],
'navigation': ['Arrow keys', 'Enter'],
'modal': ['Escape']
};
return keyboardMap[componentType] || [];
}
```
### generateStatesFromList()
Creates state objects for each state in the states array:
```typescript
function generateStatesFromList(
states: string[],
baseProperties: any,
componentType: string
): any[] {
const stateObjects = [];
for (const stateName of states) {
if (stateName === 'default') continue; // Skip default, handled separately
const stateProperties: any = {};
// State-specific border styles
if (stateName === 'hover' || stateName === 'focus') {
stateProperties.border = 'heavy';
} else if (stateName === 'disabled') {
stateProperties.border = 'dashed';
stateProperties.opacity = 0.5;
stateProperties.cursor = 'not-allowed';
} else if (stateName === 'error') {
stateProperties.border = 'heavy';
stateProperties.borderColor = 'red';
} else if (stateName === 'success') {
stateProperties.border = 'heavy';
stateProperties.borderColor = 'green';
} else if (stateName === 'loading') {
stateProperties.opacity = 0.7;
stateProperties.cursor = 'wait';
} else if (stateName === 'active') {
stateProperties.border = 'heavy';
stateProperties.background = 'filled';
}
// Copy base properties and merge with state-specific ones
stateObjects.push({
name: stateName,
properties: { ...baseProperties, ...stateProperties }
});
}
return stateObjects;
}
```
### generateMinimalDefaultState()
Creates a single default state object for MVP component creation (fast mode):
```typescript
function generateMinimalDefaultState(
visualProperties: any,
componentType: string
): any {
return {
name: 'default',
properties: {
border: visualProperties.borderStyle || 'light',
background: inferBackground(componentType),
textColor: 'default'
}
};
}
```
**Usage**: This function creates ONLY the default state, enabling fast MVP component creation. Use `generateStatesFromList()` later when expanding components with `/fluxwing-expand-component`.
**Example**:
```typescript
// Minimal mode - single state
const minimalStates = [generateMinimalDefaultState(visualProps, 'button')];
// Full mode - multiple states
const fullStates = [
generateMinimalDefaultState(visualProps, 'button'),
...generateStatesFromList(['hover', 'active', 'disabled'], baseProps, 'button')
];
```
## Layout Helpers
### inferDisplay()
Determines CSS display property based on component type:
```typescript
function inferDisplay(componentType: string): string {
const displayMap = {
'button': 'inline-block',
'input': 'inline-block',
'checkbox': 'inline-block',
'radio': 'inline-block',
'badge': 'inline',
'link': 'inline',
'text': 'inline',
'heading': 'block',
'divider': 'block',
'card': 'block',
'panel': 'block',
'modal': 'block',
'container': 'block',
'form': 'block',
'list': 'block',
'table': 'block'
};
return displayMap[componentType] || 'block';
}
```
### generateSpacing()
Calculates padding based on component dimensions (~10% of size):
```typescript
function generateSpacing(width: number, height: number): any {
const paddingX = Math.max(1, Math.floor(width * 0.1));
const paddingY = Math.max(1, Math.floor(height * 0.1));
return {
padding: {
x: paddingX,
y: paddingY
},
margin: {
x: 0,
y: 0
}
};
}
```
## Props & Variables Helpers
### extractVariables()
Extracts variable definitions from visual properties:
```typescript
function extractVariables(
visualProperties: any,
componentType: string
): any[] {
const variables: any[] = [];
// Text content variable (common to most components)
if (visualProperties.textContent) {
variables.push({
name: 'text',
type: 'string',
required: true,
default: visualProperties.textContent,
description: `${componentType} label text`
});
}
// Placeholder variable (for inputs)
if (visualProperties.placeholder) {
variables.push({
name: 'placeholder',
type: 'string',
required: false,
default: visualProperties.placeholder,
description: 'Placeholder text when empty'
});
}
// Value variable (for inputs/displays)
if (['input', 'select', 'text'].includes(componentType)) {
variables.push({
name: 'value',
type: 'string',
required: false,
default: '',
description: 'Current value'
});
}
// Variant variable (for buttons)
if (componentType === 'button') {
variables.push({
name: 'variant',
type: 'string',
required: false,
default: 'primary',
description: 'Button style variant (primary, secondary, danger)'
});
}
// Size variable (for scalable components)
if (['button', 'input', 'badge'].includes(componentType)) {
variables.push({
name: 'size',
type: 'string',
required: false,
default: 'medium',
description: 'Component size (small, medium, large)'
});
}
return variables;
}
```
### extractPropsFromVisualProperties()
Extract component props based on type and visual properties:
```typescript
function extractPropsFromVisualProperties(
visualProperties: any,
componentType: string
): any {
const props: any = {};
// Text content (most components)
if (visualProperties.textContent) {
props.text = visualProperties.textContent;
}
// Placeholder (inputs)
if (visualProperties.placeholder) {
props.placeholder = visualProperties.placeholder;
}
// Type-specific props
if (componentType === 'button') {
props.variant = inferButtonVariant(visualProperties);
props.size = inferSize(visualProperties.width, visualProperties.height);
}
if (componentType === 'input') {
props.type = inferInputType(visualProperties);
props.size = inferSize(visualProperties.width, visualProperties.height);
props.maxLength = Math.floor(visualProperties.width * 0.8);
}
if (componentType === 'checkbox' || componentType === 'radio') {
props.label = visualProperties.textContent;
props.checked = false; // Default unchecked
}
if (componentType === 'badge') {
props.variant = inferBadgeVariant(visualProperties);
}
if (componentType === 'icon') {
props.name = visualProperties.textContent || 'icon';
props.size = inferSize(visualProperties.width, visualProperties.height);
}
return props;
}
```
## Inference Helpers
### inferButtonVariant()
```typescript
function inferButtonVariant(vp: any): string {
// Primary buttons typically have filled backgrounds
// Secondary buttons have borders
if (vp.borderStyle === 'heavy' || vp.borderStyle === 'double') {
return 'primary';
}
return 'secondary';
}
```
### inferInputType()
```typescript
function inferInputType(vp: any): string {
const text = vp.textContent?.toLowerCase() || '';
const placeholder = vp.placeholder?.toLowerCase() || '';
if (text.includes('password') || placeholder.includes('password')) {
return 'password';
}
if (text.includes('email') || placeholder.includes('email')) {
return 'email';
}
if (text.includes('number') || placeholder.includes('number')) {
return 'number';
}
if (text.includes('search')) {
return 'search';
}
return 'text';
}
```
### inferSize()
```typescript
function inferSize(width: number, height: number): string {
// Small: < 20 width or < 3 height
// Medium: 20-40 width, 3-5 height
// Large: > 40 width or > 5 height
if (width < 20 || height < 3) return 'small';
if (width > 40 || height > 5) return 'large';
return 'medium';
}
```
### inferBadgeVariant()
```typescript
function inferBadgeVariant(vp: any): string {
// Could be inferred from color analysis in future
// For now, default to neutral
return 'neutral';
}
```
### inferAdditionalTags()
```typescript
function inferAdditionalTags(type: string, vp: any): string[] {
const tags: string[] = [];
// Add category tags
const category = mapTypeToCategory(type);
if (category !== 'custom') {
tags.push(category);
}
// Add interaction tags
if (isFocusable(type)) {
tags.push('interactive');
}
return tags;
}
```
## Usage Pattern
These helper functions work together to transform vision analysis into complete .uxm files:
```typescript
// Example: Generate atomic component .uxm
function generateAtomicUXM(componentData: any, timestamp: string): any {
const { id, type, visualProperties, states, accessibility } = componentData;
return {
"id": id,
"type": type,
"version": "1.0.0",
"metadata": {
"name": generateComponentName(id),
"description": generateComponentDescription(type, visualProperties, accessibility),
"author": "Fluxwing Screenshot Import",
"created": timestamp,
"modified": timestamp,
"tags": [type, "imported", "screenshot-generated", ...inferAdditionalTags(type, visualProperties)],
"category": mapTypeToCategory(type)
},
"props": extractPropsFromVisualProperties(visualProperties, type),
"behavior": {
"states": [
{
"name": "default",
"properties": {
"border": visualProperties.borderStyle,
"background": inferBackground(type),
"textColor": "default"
}
},
...generateStatesFromList(
states.filter(s => s !== 'default'),
{ border: visualProperties.borderStyle, background: inferBackground(type), textColor: "default" },
type
)
],
"interactions": generateInteractions(type),
"accessibility": {
"role": accessibility.role,
"focusable": isFocusable(type),
"keyboardSupport": generateKeyboardSupport(type),
"ariaLabel": accessibility.label || visualProperties.textContent
}
},
"layout": {
"display": inferDisplay(type),
"positioning": "static",
"spacing": generateSpacing(visualProperties.width, visualProperties.height),
"sizing": {
"minWidth": visualProperties.width,
"height": visualProperties.height
}
},
"ascii": {
"templateFile": `${id}.md`,
"width": visualProperties.width,
"height": visualProperties.height,
"variables": extractVariables(visualProperties, type)
}
};
}
```
## Reference
These functions implement the uxscii component specification documented in:
- `fluxwing/data/docs/01-uxscii-specification.md` - Format specification
- `fluxwing/data/docs/03-component-creation.md` - Component structure
- `fluxwing/data/schema/uxm-component.schema.json` - JSON Schema validation
Use these helpers to ensure consistent, valid component generation from screenshot analysis data.