Files
gh-trabian-fluxwing-skills/skills/fluxwing-screenshot-importer/docs/screenshot-import-helpers.md
2025-11-30 09:02:33 +08:00

16 KiB

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:

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:

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:

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:

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:

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:

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:

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:

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):

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:

// 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:

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):

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:

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:

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()

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()

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()

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()

function inferBadgeVariant(vp: any): string {
  // Could be inferred from color analysis in future
  // For now, default to neutral
  return 'neutral';
}

inferAdditionalTags()

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:

// 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.