SuperAPI AI Directory

react-to-axure

Frontend & websites

View repository

Updated 10-16 00:00

About this skill

React Export to Axure

Overview

This skill will help you transformany React component or applicationinto a standard component that can be imported into the Axure prototyping platform. After the transformation is complete, the component will have:

  • Configuration Panel: Provides visual configuration items (colors, text, numeric values, etc.) in the Axure editor
  • Event System: Sends custom events to the Axure prototype
  • Action System: Responds to external calls from the Axure prototype
  • Variable Exposure: Exposes internal state for the Axure prototype to read
  • Data Access: Receives external data sources from the Axure prototype

This skill produces two types of artifacts:

  1. Configuration Declarations: 5 groups of constants (EVENT_LIST / ACTION_LIST / VAR_LIST / CONFIG_LIST / DATA_LIST)
  2. Runtime Wrapper: Uses forwardRef + useImperativeHandle wrapped component code

At the same time, Axure export artifacts intended for consumption by host tools are written by default to the general artifact directory:

.axhub/make/artifacts/axure/<resource-id>/
├── index-bundle.json
├── axure-json.json
├── cover.svg
└── manifest.json

If the target project uses .axhub/make/project.json to describe resources, these paths need to be registered in the corresponding resources.prototypes[].artifacts.axure so that make-server or other hosts can directly read, copy, and download them.

When to Use

  • You have an existing React component and want to use it in an Axure prototype
  • Your React application needs to interact bidirectionally with the Axure prototype system
  • You need to generate a standardized configuration panel definition for a React component

Referenced Files

This skill comes with complete type definition files that can be copied directly into the target project for use:

  • references/axure-types.ts: Core interfaces (AxureProps, AxureHandle) and utility functions
  • references/config-panel-types.ts: Complete type definitions for configuration panel components

.axhub/make/project.json Example of resource metadata in :

{
  "artifacts": {
    "axure": {
      "caseId": "home",
      "manifestPath": ".axhub/make/artifacts/axure/home/manifest.json",
      "indexBundlePath": ".axhub/make/artifacts/axure/home/index-bundle.json",
      "axureJsonPath": ".axhub/make/artifacts/axure/home/axure-json.json",
      "coverSvgPath": ".axhub/make/artifacts/axure/home/cover.svg"
    }
  }
}

Transformation Workflow

Step 1: Analyze the Source Component

Analyze the following aspects of the target React component:

  1. Props Interface: Which props does the component currently receive?
  2. Internal State: Which useState / useReducer states need to be exposed?
  3. Interaction Behavior: Which user interaction events are there (click, change, submit, etc.)?
  4. External Dependencies: Which third-party libraries does the component depend on?
  5. DOM Manipulation: Does it directly manipulate the DOM (such as chart libraries, map libraries)?

Output a transformation checklist listing the props → config, state → var, callback → event/action mappings that need to be made.

Step 2: Copy the Type Definitions

Copy references/axure-types.ts to a public directory of the target project (such as src/common/axure-types.ts). If the detailed types for the configuration panel are needed, also copy references/config-panel-types.ts。

Step 3: Define the Interface Layer

Based on the analysis results, generate the following 5 groups of constant definitions.

3.1 Event List EVENT_LIST

Define the events that the component can trigger externally:

import type { EventItem } from './path-to/axure-types';

const EVENT_LIST: EventItem[] = [
  { name: 'onClick', desc: '点击时触发' },
  { name: 'onChange', desc: '值改变时触发,payload 为新值的 JSON 字符串' }
];

Rules:

  • name: Event name, camelCase is recommended
  • desc: Event description, explaining the trigger timing
  • payload: Optional, describes the payload format (payload must be of string type; complex data requires JSON.stringify)

3.2 Action list ACTION_LIST

Defines the external calls that the component can respond to:

import type { Action } from './path-to/axure-types';

const ACTION_LIST: Action[] = [
  { name: 'reset', desc: '重置组件到初始状态' },
  { name: 'setValue', desc: '设置值,参数格式:JSON 字符串', params: 'JSON string' }
];

Rules:

  • params must be of string type; complex parameters require JSON.parse parsing
  • desc should describe the parameter format

3.3 Variable list VAR_LIST

Defines the internal state exposed by the component:

import type { KeyDesc } from './path-to/axure-types';

const VAR_LIST: KeyDesc[] = [
  { name: 'current_value', desc: '当前值' },
  { name: 'is_loading', desc: '是否加载中' }
];

Rules:

  • name must use snake_case(lowercase letters + digits + underscores)
  • Only expose state that the outside needs to read; do not expose internal implementation details

3.4 Configuration item list CONFIG_LIST

Defines the configurable items in the configuration panel:

import type { ConfigItem } from './path-to/axure-types';

const CONFIG_LIST: ConfigItem[] = [
  {
    type: 'input',
    attributeId: 'title',
    displayName: '标题',
    info: '组件顶部显示的标题文本',
    initialValue: '默认标题'
  },
  {
    type: 'colorPicker',
    attributeId: 'primaryColor',
    displayName: '主色调',
    info: '组件的主题颜色',
    initialValue: '#1890ff'
  },
  {
    type: 'inputNumber',
    attributeId: 'maxCount',
    displayName: '最大数量',
    info: '允许的最大数量',
    initialValue: 10,
    min: 1,
    max: 100
  },
  {
    type: 'switch',
    attributeId: 'showBorder',
    displayName: '显示边框',
    info: '是否显示组件边框',
    initialValue: true
  },
  {
    type: 'select',
    attributeId: 'size',
    displayName: '尺寸',
    info: '组件尺寸',
    initialValue: 'medium',
    options: [
      { label: '小', value: 'small' },
      { label: '中', value: 'medium' },
      { label: '大', value: 'large' }
    ]
  }
];

Available type(see detailed definitions in references/config-panel-types.ts):

type Purpose initialValue type
input Text input string
inputNumber Number input (supports min/max/step) number
checkbox Checkbox boolean
switch Switch boolean
slider Slider (requires min/max) number
select Dropdown select (requires options) string | number
colorPicker Color picker string(HEX format)
autoComplete Autocomplete input string
arrayData Array data editor string[]
table Table data editor any[]
map Key-value pair editor Record<string, any>
fontSetting Font settings (color/size/weight, etc.) Object
lineSetting Line settings Object
group Group container (contains children) —
collapse Collapsible panel container —

Mapping strategy (original Props → ConfigItem):

Original Props type Recommended ConfigItem type
string input
number inputNumber
boolean switch or checkbox
enum / union select(map enum values to options)
Color string colorPicker
string[] arrayData
Complex object/array table or map

3.5 Data item list DATA_LIST

Defines the external data structure received by the component:

import type { DataDesc } from './path-to/axure-types';

const DATA_LIST: DataDesc[] = [
  {
    name: 'items',
    desc: '列表数据',
    keys: [
      { name: 'id', desc: '唯一标识' },
      { name: 'title', desc: '标题' },
      { name: 'value', desc: '值' }
    ]
  }
];

Applicable scenarios: Suitable for data-driven components such as lists and tables. Purely presentational or configuration-driven components can be set to an empty array []。

Step 4: Wrap the component

Wrap the original component with forwardRef + useImperativeHandle :

import React, { forwardRef, useImperativeHandle, useState, useCallback } from 'react';
import type { AxureProps, AxureHandle } from './path-to/axure-types';

const WrappedComponent = forwardRef<AxureHandle, AxureProps>(
  function WrappedComponent(innerProps, ref) {
    // ① 安全解构 props(防御性编程)
    const dataSource = innerProps && innerProps.data ? innerProps.data : {};
    const configSource = innerProps && innerProps.config ? innerProps.config : {};
    const onEventHandler = typeof innerProps.onEvent === 'function'
      ? innerProps.onEvent
      : function () { return undefined; };
    const container = innerProps && innerProps.container ? innerProps.container : null;

    // ② 从 config 读取配置值(使用类型检查,避免 || 运算符)
    const title = typeof configSource.title === 'string' && configSource.title
      ? configSource.title
      : '默认标题';
    const maxCount = typeof configSource.maxCount === 'number'
      ? configSource.maxCount
      : 10;

    // ③ 组件内部状态
    const countState = useState(0);
    const count = countState[0];
    const setCount = countState[1];

    // ④ 事件触发(payload 必须是字符串)
    const emitEvent = useCallback(function (eventName: string, payload?: string) {
      try {
        onEventHandler(eventName, payload);
      } catch (error) {
        console.warn('事件触发失败:', eventName, error);
      }
    }, [onEventHandler]);

    // ⑤ 动作处理(params 必须是字符串)
    const handleAction = useCallback(function (name: string, params?: string) {
      switch (name) {
        case 'reset':
          setCount(0);
          break;
        case 'setValue':
          if (params) {
            try {
              const parsed = JSON.parse(params);
              if (typeof parsed.count === 'number') {
                setCount(parsed.count);
              }
            } catch (e) {
              console.warn('参数解析失败:', e);
            }
          }
          break;
      }
    }, []);

    // ⑥ 暴露 Handle 接口
    useImperativeHandle(ref, function () {
      return {
        getVar: function (name: string) {
          const vars: Record<string, any> = {
            current_value: count,
          };
          return vars[name];
        },
        fireAction: handleAction,
        eventList: EVENT_LIST,
        actionList: ACTION_LIST,
        varList: VAR_LIST,
        configList: CONFIG_LIST,
        dataList: DATA_LIST
      };
    }, [count, handleAction]);

    // ⑦ 渲染原始组件(透传改造后的 props)
    return <OriginalComponent title={title} maxCount={maxCount} />;
  }
);

export default WrappedComponent;

Step 5: Adapt the build output

The build artifact must meet the following requirements:

  • Output format: IIFE (Immediately Invoked Function Expression), with the global variable name UserComponent
  • Externalized dependencies: React and ReactDOM must be externalized and not bundled into the artifact
  • Single-file output: The final artifact is a single JS file + an optional CSS file

Vite build configuration example:

// vite.config.js
export default {
  build: {
    lib: {
      entry: 'src/index.tsx',
      name: 'UserComponent',
      formats: ['iife'],
      fileName: () => 'index.js'
    },
    rollupOptions: {
      external: ['react', 'react-dom'],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM'
        }
      }
    }
  }
};

Step 6: Verification checklist

After the refactoring is complete, check item by item:

  • The component uses forwardRef<AxureHandle, AxureProps> wrapper
  • useImperativeHandle correctly exposes all 5 lists (eventList, actionList, varList, configList, dataList)
  • export default exports the wrapped component
  • All event payloads are string type
  • All action params are string type
  • The name in VAR_LIST uses snake_case
  • Each item in CONFIG_LIST has type、attributeId、displayName、initialValue
  • props destructuring uses defensive programming (innerProps && innerProps.xxx), does not use ES6 destructuring
  • config value reading uses type checking, does not use the || operator (avoids 0/false being misjudged)
  • The build output format is IIFE, the global variable name is UserComponent
  • React/ReactDOM are externalized, not bundled again

Conversion Examples

Example 1: Simple display component

Before conversion(original React component):

function StatusCard({ title, status, color }) {
  return (
    <div style={{ borderLeft: `4px solid ${color}` }}>
      <h3>{title}</h3>
      <span>{status}</span>
    </div>
  );
}

After conversion(Axure-compatible component):

import React, { forwardRef, useImperativeHandle } from 'react';
import type { AxureProps, AxureHandle, EventItem, Action, KeyDesc, ConfigItem, DataDesc } from './path-to/axure-types';

const EVENT_LIST: EventItem[] = [];
const ACTION_LIST: Action[] = [];
const VAR_LIST: KeyDesc[] = [
  { name: 'current_status', desc: '当前状态文本' }
];
const CONFIG_LIST: ConfigItem[] = [
  { type: 'input', attributeId: 'title', displayName: '标题', initialValue: '状态卡片' },
  { type: 'input', attributeId: 'status', displayName: '状态', initialValue: '正常' },
  { type: 'colorPicker', attributeId: 'color', displayName: '状态颜色', initialValue: '#52c41a' }
];
const DATA_LIST: DataDesc[] = [];

const Component = forwardRef<AxureHandle, AxureProps>(function StatusCard(innerProps, ref) {
  const configSource = innerProps && innerProps.config ? innerProps.config : {};
  const title = typeof configSource.title === 'string' && configSource.title ? configSource.title : '状态卡片';
  const status = typeof configSource.status === 'string' && configSource.status ? configSource.status : '正常';
  const color = typeof configSource.color === 'string' && configSource.color ? configSource.color : '#52c41a';

  useImperativeHandle(ref, function () {
    return {
      getVar: function (name: string) { return name === 'current_status' ? status : undefined; },
      fireAction: function () {},
      eventList: EVENT_LIST,
      actionList: ACTION_LIST,
      varList: VAR_LIST,
      configList: CONFIG_LIST,
      dataList: DATA_LIST
    };
  }, [status]);

  return (
    <div style={{ borderLeft: '4px solid ' + color, padding: '12px' }}>
      <h3>{title}</h3>
      <span>{status}</span>
    </div>
  );
});

export default Component;

Example 2: Interactive component (with events and actions)

Before conversion:

function Counter({ initial = 0, onCountChange }) {
  const [count, setCount] = useState(initial);
  return (
    <div>
      <span>{count}</span>
      <button onClick={() => { setCount(c => c + 1); onCountChange?.(count + 1); }}>+1</button>
      <button onClick={() => setCount(0)}>重置</button>
    </div>
  );
}

After conversion:

import React, { forwardRef, useImperativeHandle, useState, useCallback } from 'react';
import type { AxureProps, AxureHandle, EventItem, Action, KeyDesc, ConfigItem, DataDesc } from './path-to/axure-types';

const EVENT_LIST: EventItem[] = [{ name: 'onCountChange', desc: '计数改变时触发' }];
const ACTION_LIST: Action[] = [
  { name: 'increment', desc: '计数 +1' },
  { name: 'reset', desc: '重置计数' },
  { name: 'setCount', desc: '设置计数值,参数:数字字符串', params: 'number string' }
];
const VAR_LIST: KeyDesc[] = [{ name: 'count', desc: '当前计数值' }];
const CONFIG_LIST: ConfigItem[] = [
  { type: 'inputNumber', attributeId: 'initial', displayName: '初始值', initialValue: 0, min: 0 }
];
const DATA_LIST: DataDesc[] = [];

const Component = forwardRef<AxureHandle, AxureProps>(function Counter(innerProps, ref) {
  const configSource = innerProps && innerProps.config ? innerProps.config : {};
  const onEventHandler = typeof innerProps.onEvent === 'function'
    ? innerProps.onEvent : function () {};
  const initial = typeof configSource.initial === 'number' ? configSource.initial : 0;

  const countState = useState(initial);
  const count = countState[0];
  const setCount = countState[1];

  const emitEvent = useCallback(function (eventName: string, payload?: string) {
    try { onEventHandler(eventName, payload); } catch (e) { console.warn(e); }
  }, [onEventHandler]);

  const increment = useCallback(function () {
    setCount(function (prev) {
      var next = prev + 1;
      emitEvent('onCountChange', String(next));
      return next;
    });
  }, [emitEvent]);

  const reset = useCallback(function () {
    setCount(initial);
    emitEvent('onCountChange', String(initial));
  }, [initial, emitEvent]);

  const handleAction = useCallback(function (name: string, params?: string) {
    switch (name) {
      case 'increment': increment(); break;
      case 'reset': reset(); break;
      case 'setCount':
        if (params) {
          var n = parseInt(params, 10);
          if (!isNaN(n)) { setCount(n); emitEvent('onCountChange', String(n)); }
        }
        break;
    }
  }, [increment, reset, emitEvent]);

  useImperativeHandle(ref, function () {
    return {
      getVar: function (name: string) { return name === 'count' ? count : undefined; },
      fireAction: handleAction,
      eventList: EVENT_LIST,
      actionList: ACTION_LIST,
      varList: VAR_LIST,
      configList: CONFIG_LIST,
      dataList: DATA_LIST
    };
  }, [count, handleAction]);

  return (
    <div>
      <span>{count}</span>
      <button onClick={increment}>+1</button>
      <button onClick={reset}>重置</button>
    </div>
  );
});

export default Component;

Example 3: Data-driven component

Before conversion:

function UserList({ users, onSelect }) {
  return (
    <ul>
      {users.map(u => (
        <li key={u.id} onClick={() => onSelect(u)}>
          {u.name} - {u.email}
        </li>
      ))}
    </ul>
  );
}

After conversion:

import React, { forwardRef, useImperativeHandle, useState, useCallback } from 'react';
import type { AxureProps, AxureHandle, EventItem, Action, KeyDesc, ConfigItem, DataDesc } from './path-to/axure-types';

const EVENT_LIST: EventItem[] = [{ name: 'onSelect', desc: '选中用户时触发,payload 为用户 JSON' }];
const ACTION_LIST: Action[] = [{ name: 'clearSelection', desc: '清除选中' }];
const VAR_LIST: KeyDesc[] = [{ name: 'selected_user_id', desc: '当前选中的用户 ID' }];
const CONFIG_LIST: ConfigItem[] = [
  { type: 'input', attributeId: 'emptyText', displayName: '空状态文本', initialValue: '暂无数据' }
];
const DATA_LIST: DataDesc[] = [
  {
    name: 'users',
    desc: '用户列表数据',
    keys: [
      { name: 'id', desc: '用户 ID' },
      { name: 'name', desc: '用户姓名' },
      { name: 'email', desc: '用户邮箱' }
    ]
  }
];

const Component = forwardRef<AxureHandle, AxureProps>(function UserList(innerProps, ref) {
  const dataSource = innerProps && innerProps.data ? innerProps.data : {};
  const configSource = innerProps && innerProps.config ? innerProps.config : {};
  const onEventHandler = typeof innerProps.onEvent === 'function'
    ? innerProps.onEvent : function () {};
  const emptyText = typeof configSource.emptyText === 'string' && configSource.emptyText
    ? configSource.emptyText : '暂无数据';
  var users = Array.isArray(dataSource.users) ? dataSource.users : [];

  const selectedState = useState<string | null>(null);
  const selectedId = selectedState[0];
  const setSelectedId = selectedState[1];

  const emitEvent = useCallback(function (eventName: string, payload?: string) {
    try { onEventHandler(eventName, payload); } catch (e) { console.warn(e); }
  }, [onEventHandler]);

  const handleAction = useCallback(function (name: string) {
    if (name === 'clearSelection') setSelectedId(null);
  }, []);

  useImperativeHandle(ref, function () {
    return {
      getVar: function (name: string) { return name === 'selected_user_id' ? selectedId : undefined; },
      fireAction: handleAction,
      eventList: EVENT_LIST,
      actionList: ACTION_LIST,
      varList: VAR_LIST,
      configList: CONFIG_LIST,
      dataList: DATA_LIST
    };
  }, [selectedId, handleAction]);

  if (users.length === 0) return <div>{emptyText}</div>;

  return (
    <ul>
      {users.map(function (u: any) {
        return (
          <li key={u.id} onClick={function () {
            setSelectedId(u.id);
            emitEvent('onSelect', JSON.stringify(u));
          }}>
            {u.name} - {u.email}
          </li>
        );
      })}
    </ul>
  );
});

export default Component;

Frequently Asked Questions

Q: What should I do if the component uses chart libraries (ECharts, D3) or other libraries that directly manipulate the DOM?

Use innerProps.container to get the DOM container, and initialize the chart engine in useEffect :

const container = innerProps && innerProps.container ? innerProps.container : null;

useEffect(function () {
  if (!container) return;
  const chart = echarts.init(container);
  chart.setOption({ /* ... */ });
  return function () { chart.dispose(); };
}, [container]);

return null; // 直接使用 container 渲染时返回 null

Q: The component's props are very complex (nested objects). How do I map them to CONFIG_LIST?

  • Flatten: represent nested properties using attributeId dot-separated paths, such as 'style.fontSize'
  • Group: use type: 'group' or type: 'collapse' to organize related configuration items
  • Complex structures: use table or map type editors

Q: The original component used props destructuring and arrow functions. Do they need to be rewritten?

It is recommended to rewrite them to improve compatibility:

  • Use the function keyword instead of arrow functions
  • Use innerProps && innerProps.xxx instead of destructuring (defensive programming)
  • Use useState array index access for return values instead of destructuring

Q: How do I handle component styles?

  • Put CSS in the style.css in the same directory and import './style.css'
  • use class names with a component prefix to avoid global conflicts (such as my-chart-container)
  • avoid global style pollution

Type Reference

For the complete type definitions, see this skill's references/ directory:

  • references/axure-types.ts: core interfaces and utility functions

    • AxureProps: component Props interfaces (data, config, onEvent, container)
    • AxureHandle: component Handle interfaces (getVar, fireAction, 5 lists)
    • EventItem: event definitions { name, desc, payload? }
    • Action: action definitions { name, desc, params? }
    • KeyDesc: variable/data field definitions { name, desc }(name must be snake_case)
    • DataDesc: data source definitions { name, desc, keys: KeyDesc[] }
    • ConfigItem: configuration panel item definitions
    • safeEmitEvent、createEventEmitter: event triggering utility functions
    • getConfigValue、getDataValue: safe value access utility functions
  • references/config-panel-types.ts: detailed types for configuration panel components

    • InputConfig、InputNumberConfig、CheckboxConfig、SliderConfig
    • SelectConfig、AutoCompleteConfig、ColorPickerConfig
    • ArrayDataConfig、TableConfig、MapConfig
    • FontSettingConfig、LineSettingConfig、PointSettingConfig
    • GroupConfig: group layout