microstate

مدیریت هوشمند state machine برای حالت‌ها و انیمیشن‌های دقیق کامپوننت‌های تعاملی

این skill برای طراحی و پیاده‌سازی state machine در کامپوننت‌هایی مثل button، input، toggle، checkbox، accordion و dropdown است. حالت‌هایی مانند hover، focus، loading، success، error و disabled را تعریف می‌کند و انتقال‌های معتبر، اولویت‌ها و انیمیشن هر حالت را مشخص می‌سازد.

با این رویکرد، رفتار کامپوننت‌های پیچیده قابل‌پیش‌بینی، قابل‌نگهداری و سازگار با ARIA می‌شود. الگوهای آماده برای GSAP و Framer Motion، پشتیبانی از reduced motion و هماهنگی با اصول Material Design 3، ساخت تجربه‌های تعاملی حرفه‌ای را ساده‌تر می‌کند.

چه چیزی می‌گیرید

  • مدل state machine برای حالت‌ها و transitionهای معتبر کامپوننت
  • الگوهای آماده برای button، form input، checkbox، toggle، accordion و dropdown
  • تعریف دقیق timing، easing، spring، shake، pulse و auto-transition
  • راهنمای اولویت‌دهی بین stateهای هم‌زمان مانند hover، focus، loading و error
  • ملاحظات ARIA و prefers-reduced-motion برای دسترس‌پذیری بهتر

برای چه کاری خوب است

  • تیم‌هایی که کامپوننت‌های تعاملی پیچیده و چندحالته می‌سازند
  • پروژه‌های React یا وب که از GSAP یا Framer Motion استفاده می‌کنند
  • طراحی سیستم‌هایی که رفتار، انیمیشن و دسترس‌پذیری باید هماهنگ و قابل‌پیش‌بینی باشد

برای چه کاری نیست

  • انیمیشن‌های مستقل و تزئینی که به مدیریت state یا transitionهای پیچیده نیاز ندارند
  • پروژه‌های بسیار ساده با کامپوننت‌های static و بدون تعامل چندمرحله‌ای

دربارهٔ این اسکیل

Microstate

Component-level state machine orchestration. Define granular states, valid transitions, and state-specific motion for interactive UI components.

Standards Reference

StandardDocumentation
W3C DTCGdesigntokens.org
XStateState machine patterns
Material Design 3m3.material.io/styles/motion
ARIAState management for accessibility

Quick Reference

Load from: tokens/microstate.tokens.json

Core States

StateVisual ProfileMotionStable?
idleBase appearanceNoneYes
hover+2% scale, 8% state layer100msNo
focusFocus ring50msNo
active-2% scale, 12% state layer50msNo
disabled38% opacityNoneYes
loading70% opacity, pulse1200ms loopNo
successGreen, checkmark300ms springTransient
errorRed, shake400msNeeds resolution
selectedAccent background150msYes
dragging+5% scale, elevatedFollow pointerNo

State Machine Pattern

                    ┌─────────────────────────────────┐
                    │                                 │
         ┌──────────▼───────────┐                     │
    ┌───►│        IDLE          │◄────────────────────┤
    │    └──────────┬───────────┘                     │
    │               │ pointerenter                    │
    │    ┌──────────▼───────────┐                     │
    │    │        HOVER         │──────┐              │
    │    └──────────┬───────────┘      │              │
    │               │ pointerdown      │ pointerleave │
    │    ┌──────────▼───────────┐      │              │
    │    │       ACTIVE         │──────┼──────────────┘
    │    └──────────┬───────────┘      │
    │               │                  │
    │    ┌──────────┼──────────┐       │
    │    │          │          │       │
    │    ▼          ▼          ▼       │
    │ SUCCESS   LOADING     ERROR      │
    │    │          │          │       │
    │    └──────────┴──────────┴───────┘
    │               │
    │               │ timeout / dismiss
    └───────────────┘

Selection Guide

What component type?

├── Button (click action)
│   └── States: idle, hover, focus, active, loading, success, error, disabled
│
├── Form input
│   └── States: empty, focus, filled, error, success, disabled
│
├── Checkbox/Radio
│   └── States: unchecked, hover, focus, active, checked, indeterminate, disabled
│
├── Toggle switch
│   └── States: off, hover-off, on, hover-on, focus, disabled
│
├── Card (interactive)
│   └── States: idle, hover, focus, active, expanded, selected, dragging
│
├── Accordion
│   └── States: collapsed, hover, expanding, expanded, collapsing
│
└── Dropdown
    └── States: closed, opening, open, closing, disabled

State Implementations

Button State Machine

type ButtonState = 'idle' | 'hover' | 'focus' | 'active' | 'loading' | 'success' | 'error' | 'disabled';

const buttonMachine = {
  initial: 'idle',
  states: {
    idle: {
      style: { scale: 1, opacity: 1 },
      on: {
        POINTER_ENTER: 'hover',
        FOCUS: 'focus',
        DISABLE: 'disabled'
      }
    },
    hover: {
      style: {
        scale: 1.02,
        boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
      },
      transition: { duration: 100, ease: 'standard' },
      on: {
        POINTER_LEAVE: 'idle',
        POINTER_DOWN: 'active',
        FOCUS: 'focus'
      }
    },
    focus: {
      style: { outline: '3px solid var(--focus-ring)' },
      transition: { duration: 50 },
      parallel: true, // Can coexist with hover
      on: {
        BLUR: 'idle',
        POINTER_DOWN: 'active'
      }
    },
    active: {
      style: { scale: 0.98 },
      transition: { duration: 50, ease: 'emphasized-accelerate' },
      on: {
        POINTER_UP: 'idle',
        SUBMIT: 'loading',
        SUCCESS: 'success',
        ERROR: 'error'
      }
    },
    loading: {
      style: { opacity: 0.7 },
      animation: { type: 'pulse', duration: 1200, repeat: true },
      on: {
        COMPLETE: 'success',
        FAIL: 'error'
      }
    },
    success: {
      style: { backgroundColor: 'var(--success)' },
      transition: { duration: 200, type: 'spring' },
      transient: true,
      autoExit: { to: 'idle', delay: 2000 },
      on: {}
    },
    error: {
      style: { backgroundColor: 'var(--error)' },
      animation: { type: 'shake', duration: 400 },
      on: {
        DISMISS: 'idle',
        RETRY: 'active'
      }
    },
    disabled: {
      style: { opacity: 0.38, pointerEvents: 'none' },
      on: {
        ENABLE: 'idle'
      }
    }
  }
};

GSAP Implementation:

class ButtonStateMachine {
  constructor(element) {
    this.element = element;
    this.state = 'idle';
    this.setupListeners();
  }

  transition(newState) {
    const fromState = this.state;
    const toState = newState;

    // Kill any ongoing animations
    gsap.killTweensOf(this.element);

    // Get transition config
    const config = this.getTransitionConfig(fromState, toState);

    // Animate
    gsap.to(this.element, {
      ...config.style,
      duration: config.duration / 1000,
      ease: config.ease
    });

    // Handle special animations
    if (config.animation) {
      this.playAnimation(config.animation);
    }

    // Handle auto-exit
    if (config.autoExit) {
      setTimeout(() => {
        this.transition(config.autoExit.to);
      }, config.autoExit.delay);
    }

    this.state = newState;
  }

  getTransitionConfig(from, to) {
    const transitions = {
      'idle-hover': {
        style: { scale: 1.02, boxShadow: '0 2px 8px rgba(0,0,0,0.1)' },
        duration: 100,
        ease: 'power2.out'
      },
      'hover-idle': {
        style: { scale: 1, boxShadow: 'none' },
        duration: 150,
        ease: 'power2.out'
      },
      'hover-active': {
        style: { scale: 0.98 },
        duration: 50,
        ease: 'power2.in'
      },
      'active-success': {
        style: { backgroundColor: '#4CAF50', scale: 1.05 },
        duration: 200,
        ease: 'back.out(2)',
        autoExit: { to: 'idle', delay: 2000 }
      },
      'active-error': {
        style: { backgroundColor: '#f44336' },
        duration: 150,
        animation: { type: 'shake' }
      }
    };

    return transitions[`${from}-${to}`] || { style: {}, duration: 200 };
  }

  playAnimation(config) {
    if (config.type === 'shake') {
      gsap.to(this.element, {
        x: 10,
        duration: 0.08,
        repeat: 3,
        yoyo: true,
        ease: 'power2.inOut'
      });
    } else if (config.type === 'pulse') {
      gsap.to(this.element, {
        opacity: 0.6,
        duration: 0.6,
        repeat: -1,
        yoyo: true,
        ease: 'sine.inOut'
      });
    }
  }

  setupListeners() {
    this.element.addEventListener('pointerenter', () => {
      if (this.state === 'idle') this.transition('hover');
    });

    this.element.addEventListener('pointerleave', () => {
      if (this.state === 'hover') this.transition('idle');
    });

    this.element.addEventListener('pointerdown', () => {
      if (['idle', 'hover'].includes(this.state)) this.transition('active');
    });

    this.element.addEventListener('pointerup', () => {
      if (this.state === 'active') this.transition('hover');
    });
  }
}

Checkbox State Machine

const checkboxMachine = {
  initial: 'unchecked',
  states: {
    unchecked: {
      style: { '--check-scale': 0 },
      on: { TOGGLE: 'checking' }
    },
    checking: {
      animation: {
        '--check-scale': [0, 1.2, 1],
        duration: 150,
        ease: 'back.out(2)'
      },
      autoTransition: { to: 'checked', delay: 150 }
    },
    checked: {
      style: { '--check-scale': 1 },
      on: { TOGGLE: 'unchecking' }
    },
    unchecking: {
      animation: {
        '--check-scale': [1, 0],
        duration: 100,
        ease: 'power2.in'
      },
      autoTransition: { to: 'unchecked', delay: 100 }
    }
  }
};

// CSS for checkbox
/*
.checkbox-mark {
  transform: scale(var(--check-scale, 0));
  transform-origin: center;
}
*/

Toggle Switch

const toggleMachine = {
  initial: 'off',
  states: {
    off: {
      style: {
        '--thumb-x': '0%',
        '--track-bg': 'var(--neutral)'
      },
      on: { TOGGLE: 'transitioning-on' }
    },
    'transitioning-on': {
      animation: {
        '--thumb-x': ['0%', '100%'],
        '--track-bg': ['var(--neutral)', 'var(--primary)'],
        duration: 200,
        ease: 'spring'
      },
      autoTransition: { to: 'on', delay: 200 }
    },
    on: {
      style: {
        '--thumb-x': '100%',
        '--track-bg': 'var(--primary)'
      },
      on: { TOGGLE: 'transitioning-off' }
    },
    'transitioning-off': {
      animation: {
        '--thumb-x': ['100%', '0%'],
        '--track-bg': ['var(--primary)', 'var(--neutral)'],
        duration: 200,
        ease: 'spring'
      },
      autoTransition: { to: 'off', delay: 200 }
    }
  }
};

Form Input

const inputMachine = {
  initial: 'empty',
  states: {
    empty: {
      style: { '--label-y': '50%', '--label-scale': 1 },
      on: {
        FOCUS: 'focus-empty',
        INPUT: 'filled'
      }
    },
    'focus-empty': {
      style: {
        '--label-y': '0%',
        '--label-scale': 0.75,
        '--border-color': 'var(--primary)'
      },
      transition: { duration: 150 },
      on: {
        BLUR: 'empty',
        INPUT: 'focus-filled'
      }
    },
    'focus-filled': {
      style: {
        '--label-y': '0%',
        '--label-scale': 0.75,
        '--border-color': 'var(--primary)'
      },
      on: {
        BLUR: 'filled',
        CLEAR: 'focus-empty'
      }
    },
    filled: {
      style: {
        '--label-y': '0%',
        '--label-scale': 0.75,
        '--border-color': 'var(--neutral)'
      },
      on: {
        FOCUS: 'focus-filled',
        CLEAR: 'empty',
        VALIDATE_ERROR: 'error',
        VALIDATE_SUCCESS: 'success'
      }
    },
    error: {
      style: {
        '--border-color': 'var(--error)',
        '--shake': 1
      },
      animation: { type: 'shake', duration: 400 },
      on: {
        FOCUS: 'focus-filled',
        INPUT: 'filled'
      }
    },
    success: {
      style: { '--border-color': 'var(--success)' },
      on: {
        FOCUS: 'focus-filled',
        INPUT: 'filled'
      }
    }
  }
};

Accordion

const accordionMachine = {
  initial: 'collapsed',
  states: {
    collapsed: {
      style: {
        '--content-height': '0px',
        '--icon-rotation': '0deg'
      },
      on: { TOGGLE: 'expanding' }
    },
    expanding: {
      animation: {
        '--content-height': ['0px', 'var(--full-height)'],
        '--icon-rotation': ['0deg', '180deg'],
        duration: 300,
        ease: 'emphasized'
      },
      autoTransition: { to: 'expanded', delay: 300 }
    },
    expanded: {
      style: {
        '--content-height': 'var(--full-height)',
        '--icon-rotation': '180deg'
      },
      on: { TOGGLE: 'collapsing' }
    },
    collapsing: {
      animation: {
        '--content-height': ['var(--full-height)', '0px'],
        '--icon-rotation': ['180deg', '0deg'],
        duration: 250,
        ease: 'emphasized-accelerate'
      },
      autoTransition: { to: 'collapsed', delay: 250 }
    }
  }
};

Framer Motion Implementation

import { motion, AnimatePresence, useReducedMotion } from 'framer-motion';

function StatefulButton({ onClick, children }) {
  const [state, setState] = useState<ButtonState>('idle');
  const shouldReduceMotion = useReducedMotion();

  const variants = {
    idle: { scale: 1, opacity: 1 },
    hover: { scale: shouldReduceMotion ? 1 : 1.02 },
    active: { scale: shouldReduceMotion ? 1 : 0.98 },
    loading: { opacity: 0.7 },
    success: { backgroundColor: '#4CAF50' },
    error: { x: [0, -10, 10, -10, 10, 0] }
  };

  return (
    <motion.button
      variants={variants}
      animate={state}
      onHoverStart={() => setState('hover')}
      onHoverEnd={() => setState('idle')}
      onTapStart={() => setState('active')}
      onTap={async () => {
        setState('loading');
        try {
          await onClick();
          setState('success');
          setTimeout(() => setState('idle'), 2000);
        } catch {
          setState('error');
        }
      }}
      transition={{
        duration: state === 'error' ? 0.4 : 0.1,
        ease: state === 'success' ? [0.34, 1.56, 0.64, 1] : 'easeOut'
      }}
    >
      {children}
    </motion.button>
  );
}

Transition Priorities

When multiple state changes compete:

PriorityRule
1Interrupt current for error
2Queue for loading to success/error
3Blend for hover/focus coexistence

Reduced Motion

For prefers-reduced-motion: reduce:

Full MotionReduced
Scale changesOpacity only
Shake animationStatic error indicator
Spring transitionsInstant state
Hover elevationBorder/outline only

Cross-References

  • See state-layer/ for M3 state layer tokens
  • See emotion-mapping/ for emotional state profiles
  • See consequence/ for multi-component state chains
  • See accessibility/ for ARIA state management

References

  • references/state-chart-patterns.md — Complete state chart patterns

دانلود ZIP