@momo-kits/mcp-expo 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -0
- package/package.json +21 -0
- package/server.mjs +308 -0
- package/template/.expo/README.md +8 -0
- package/template/.expo/devices.json +3 -0
- package/template/App.tsx +147 -0
- package/template/app.json +10 -0
- package/template/package-lock.json +8120 -0
- package/template/package.json +20 -0
- package/template/tsconfig.json +4 -0
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# MCP Expo — AI Code → Expo Go Demo
|
|
2
|
+
|
|
3
|
+
MCP server nhận code từ AI tool, ghi vào `expo-demo-template/App.tsx`, và trả URL để mở app trên Expo Go.
|
|
4
|
+
|
|
5
|
+
## Cấu trúc
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
/Users/sonnguyen/Documents/Momo/
|
|
9
|
+
├── expo-demo-template/ ← App Expo cố định (Metro đọc ở đây)
|
|
10
|
+
│ ├── App.tsx ← File bị ghi đè mỗi lần gọi tool
|
|
11
|
+
│ ├── app.json
|
|
12
|
+
│ └── package.json
|
|
13
|
+
│
|
|
14
|
+
└── mcp-expo/ ← MCP server
|
|
15
|
+
├── server.mjs ← MCP server (ESM, stdio)
|
|
16
|
+
└── package.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd /Users/sonnguyen/Documents/Momo/mcp-expo
|
|
23
|
+
npm install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Chạy MCP
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node server.mjs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Cấu hình MCP client (Claude Code / Claude Desktop):
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"expo-demo": {
|
|
38
|
+
"command": "node",
|
|
39
|
+
"args": ["/Users/sonnguyen/Documents/Momo/mcp-expo/server.mjs"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Tools
|
|
46
|
+
|
|
47
|
+
### `push_screen_and_run`
|
|
48
|
+
|
|
49
|
+
Ghi code AI vào `App.tsx`, khởi động Metro nếu cần, trả URL.
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"code": "import React from 'react';\nimport { View, Text } from 'react-native';\n\nexport default function Demo() {\n return <View><Text>Hello!</Text></View>;\n}",
|
|
54
|
+
"reset": false
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
| Param | Type | Mô tả |
|
|
59
|
+
|-------|------|--------|
|
|
60
|
+
| `code` | string | Nội dung TSX/JSX từ AI |
|
|
61
|
+
| `reset` | boolean | `true` = kill + restart Metro trước. Mặc định: `false` |
|
|
62
|
+
|
|
63
|
+
Response:
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"ok": true,
|
|
67
|
+
"message": "Code updated. App is reloading via HMR...",
|
|
68
|
+
"expoUrl": "http://localhost:8081"
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `stop_expo`
|
|
73
|
+
Kill Metro server đang chạy.
|
|
74
|
+
|
|
75
|
+
### `get_expo_status`
|
|
76
|
+
Kiểm tra trạng thái:
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"isRunning": true,
|
|
80
|
+
"expoUrl": "http://localhost:8081",
|
|
81
|
+
"pid": 12345
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Flow
|
|
86
|
+
|
|
87
|
+
1. AI gen code → gọi `push_screen_and_run`
|
|
88
|
+
2. MCP ghi code vào `App.tsx`
|
|
89
|
+
3. Metro bundler khởi động (hoặc reload HMR nếu đang chạy)
|
|
90
|
+
4. Trả URL → mở Expo Go trên điện thoại, trỏ vào URL đó
|
|
91
|
+
5. App hiện màn hình AI vừa gen
|
|
92
|
+
|
|
93
|
+
## Lưu ý
|
|
94
|
+
|
|
95
|
+
- Metro chạy ở `--lan` (cùng WiFi). URL: `http://<IP>:8081`
|
|
96
|
+
- Expo Go cần kết nối cùng mạng WiFi với máy tính
|
|
97
|
+
- `CI=1` được set tự động để tránh interactive prompts
|
|
98
|
+
- Nếu máy có firewall, allow port 8081
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@momo-kits/mcp-expo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server: push AI-generated code to Expo and get LAN URL for Expo Go demo",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "server.mjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-expo": "./server.mjs"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node server.mjs"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
15
|
+
},
|
|
16
|
+
"keywords": ["mcp", "expo", "react-native", "ai", "demo"],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/server.mjs
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server: expo-demo
|
|
3
|
+
*
|
|
4
|
+
* Nhận code từ AI tool, ghi vào App.tsx trong expo-demo-template,
|
|
5
|
+
* chạy Expo tunnel, trả exp:// URL + QR để demo trên Expo Go.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node server.mjs
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
12
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
|
+
import {
|
|
14
|
+
CallToolRequestSchema,
|
|
15
|
+
ListToolsRequestSchema,
|
|
16
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import fs from 'fs';
|
|
19
|
+
import os from 'os';
|
|
20
|
+
import { spawn } from 'child_process';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
|
|
23
|
+
// ── Config ───────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const TEMPLATE_DIR = path.join(__dirname, 'template');
|
|
27
|
+
const APP_FILE = path.join(TEMPLATE_DIR, 'App.tsx');
|
|
28
|
+
const METRO_START_TIMEOUT_MS = 90_000;
|
|
29
|
+
|
|
30
|
+
// ── State ─────────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
let metroProcess = null;
|
|
33
|
+
let expoUrl = null;
|
|
34
|
+
let isExpoRunning = false;
|
|
35
|
+
|
|
36
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
function log(...args) {
|
|
39
|
+
console.error('[mcp-expo]', ...args);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function writeAppTsx(code) {
|
|
43
|
+
fs.writeFileSync(APP_FILE, code, 'utf8');
|
|
44
|
+
log(`Wrote App.tsx (${code.length} chars)`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getLocalIP() {
|
|
48
|
+
const nets = os.networkInterfaces();
|
|
49
|
+
for (const name of Object.keys(nets)) {
|
|
50
|
+
for (const net of nets[name]) {
|
|
51
|
+
if (net.family === 'IPv4' && !net.internal) {
|
|
52
|
+
return net.address;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function extractExpoUrl(line) {
|
|
60
|
+
// LAN URL: exp://192.168.x.x:8081 ← ưu tiên cao nhất (--lan flag)
|
|
61
|
+
const lanMatch = line.match(/(exp(?:o)?:\/\/(?:10\.\d+\.\d+.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+|\d+\.\d+\.\d+.\d+|192\.168\.\d+.\d+):\d+)/);
|
|
62
|
+
if (lanMatch) return lanMatch[1];
|
|
63
|
+
// Any exp:// URL
|
|
64
|
+
const expMatch = line.match(/(exp(?:o)?:\/\/[^\s]+)/);
|
|
65
|
+
if (expMatch) return expMatch[1];
|
|
66
|
+
// Tunnel URL
|
|
67
|
+
const tunnelMatch = line.match(/(https?:\/\/[a-zA-Z0-9.-]+\.tunnel\.expo\.test)/);
|
|
68
|
+
if (tunnelMatch) return tunnelMatch[1];
|
|
69
|
+
// Bỏ qua localhost
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function startExpoMetro() {
|
|
74
|
+
// Auto-install dependencies if node_modules missing
|
|
75
|
+
const nodeModulesPath = path.join(TEMPLATE_DIR, 'node_modules');
|
|
76
|
+
if (!fs.existsSync(nodeModulesPath)) {
|
|
77
|
+
log('Installing template dependencies...');
|
|
78
|
+
await new Promise((res, rej) => {
|
|
79
|
+
const install = spawn('npm', ['install', '--silent'], { cwd: TEMPLATE_DIR, stdio: 'pipe' });
|
|
80
|
+
install.on('close', (code) => code === 0 ? res() : rej(new Error(`npm install failed (code ${code})`)));
|
|
81
|
+
});
|
|
82
|
+
log('Dependencies installed.');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
let settled = false;
|
|
87
|
+
|
|
88
|
+
const timer = setTimeout(() => {
|
|
89
|
+
if (!settled) {
|
|
90
|
+
settled = true;
|
|
91
|
+
reject(new Error(
|
|
92
|
+
`Metro start timeout (${METRO_START_TIMEOUT_MS / 1000}s). ` +
|
|
93
|
+
`Run 'npx expo start --lan' manually in ${TEMPLATE_DIR}.`
|
|
94
|
+
));
|
|
95
|
+
}
|
|
96
|
+
}, METRO_START_TIMEOUT_MS);
|
|
97
|
+
|
|
98
|
+
log(`Starting Expo Metro in ${TEMPLATE_DIR}...`);
|
|
99
|
+
|
|
100
|
+
metroProcess = spawn('npx', ['expo', 'start', '--lan', '--port', '8081'], {
|
|
101
|
+
cwd: TEMPLATE_DIR,
|
|
102
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
103
|
+
env: { ...process.env, FORCE_COLOR: '0' },
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
metroProcess.stdout.setEncoding('utf8');
|
|
107
|
+
metroProcess.stderr.setEncoding('utf8');
|
|
108
|
+
|
|
109
|
+
const onData = (streamName) => (data) => {
|
|
110
|
+
if (settled) return;
|
|
111
|
+
const text = data.toString();
|
|
112
|
+
const lines = text.split('\n');
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
if (line.trim()) {
|
|
115
|
+
log(`[${streamName}]`, line.trim());
|
|
116
|
+
}
|
|
117
|
+
// Expo prompts for interactive input
|
|
118
|
+
if (line.includes('Use port') && line.includes('?')) {
|
|
119
|
+
metroProcess.stdin.write('y\n');
|
|
120
|
+
log('[stdin] sent "y"');
|
|
121
|
+
}
|
|
122
|
+
const url = extractExpoUrl(line);
|
|
123
|
+
if (url && !settled) {
|
|
124
|
+
expoUrl = url;
|
|
125
|
+
settled = true;
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
resolve(url);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Expo chỉ log "Waiting on localhost" khi dùng --lan, không in LAN URL
|
|
131
|
+
if (!settled && line.includes('Waiting on http://localhost:')) {
|
|
132
|
+
const localIP = getLocalIP();
|
|
133
|
+
if (localIP) {
|
|
134
|
+
expoUrl = `exp://${localIP}:8081`;
|
|
135
|
+
log(`Detected LAN IP: ${localIP} → ${expoUrl}`);
|
|
136
|
+
settled = true;
|
|
137
|
+
clearTimeout(timer);
|
|
138
|
+
resolve(expoUrl);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
metroProcess.stdout.on('data', onData('stdout'));
|
|
146
|
+
metroProcess.stderr.on('data', onData('stderr'));
|
|
147
|
+
|
|
148
|
+
metroProcess.on('error', (err) => {
|
|
149
|
+
if (!settled) {
|
|
150
|
+
settled = true;
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
reject(new Error(`Metro process error: ${err.message}`));
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
metroProcess.on('exit', (code) => {
|
|
157
|
+
if (!settled) {
|
|
158
|
+
settled = true;
|
|
159
|
+
clearTimeout(timer);
|
|
160
|
+
reject(new Error(`Metro exited unexpectedly (code ${code}).`));
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function stopMetro() {
|
|
167
|
+
if (metroProcess) {
|
|
168
|
+
metroProcess.kill('SIGTERM');
|
|
169
|
+
metroProcess = null;
|
|
170
|
+
isExpoRunning = false;
|
|
171
|
+
expoUrl = null;
|
|
172
|
+
log('Metro stopped.');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── Tool Implementations ──────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
async function handlePushScreen({ code, reset = false }) {
|
|
179
|
+
try {
|
|
180
|
+
writeAppTsx(code);
|
|
181
|
+
|
|
182
|
+
if (reset || !metroProcess || metroProcess.exitCode !== null) {
|
|
183
|
+
stopMetro();
|
|
184
|
+
await startExpoMetro();
|
|
185
|
+
isExpoRunning = true;
|
|
186
|
+
} else {
|
|
187
|
+
log('Code updated. Metro running — app will reload via HMR.');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
content: [
|
|
192
|
+
{
|
|
193
|
+
type: 'text',
|
|
194
|
+
text: JSON.stringify(
|
|
195
|
+
{
|
|
196
|
+
ok: true,
|
|
197
|
+
message: isExpoRunning
|
|
198
|
+
? 'Code updated. App is reloading via HMR — tap "r" in terminal or pull to refresh.'
|
|
199
|
+
: 'Done. Scan QR with Expo Go.',
|
|
200
|
+
expoUrl,
|
|
201
|
+
},
|
|
202
|
+
null,
|
|
203
|
+
2
|
|
204
|
+
),
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
} catch (err) {
|
|
209
|
+
log('Error:', err.message);
|
|
210
|
+
return {
|
|
211
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: false, message: err.message }) }],
|
|
212
|
+
isError: true,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function handleStopExpo() {
|
|
218
|
+
stopMetro();
|
|
219
|
+
return {
|
|
220
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: true, message: 'Metro stopped.' }) }],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function handleGetStatus() {
|
|
225
|
+
const running = isExpoRunning && metroProcess !== null && metroProcess.exitCode === null;
|
|
226
|
+
return {
|
|
227
|
+
content: [
|
|
228
|
+
{
|
|
229
|
+
type: 'text',
|
|
230
|
+
text: JSON.stringify({ isRunning: running, expoUrl, pid: metroProcess ? metroProcess.pid : null }),
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── MCP Server ─────────────────────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
const server = new Server(
|
|
239
|
+
{ name: 'mcp-expo', version: '1.0.0' },
|
|
240
|
+
{ capabilities: { tools: {} } }
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Tool definitions
|
|
244
|
+
const tools = [
|
|
245
|
+
{
|
|
246
|
+
name: 'push_screen_and_run',
|
|
247
|
+
description:
|
|
248
|
+
'Nhận code TSX/JSX từ AI, ghi vào App.tsx trong expo-demo-template, ' +
|
|
249
|
+
'chạy (hoặc reload) Expo Metro tunnel và trả exp:// URL để mở app trên Expo Go. ' +
|
|
250
|
+
'Nếu Metro đang chạy, app sẽ reload tự động qua HMR.',
|
|
251
|
+
inputSchema: {
|
|
252
|
+
type: 'object',
|
|
253
|
+
properties: {
|
|
254
|
+
code: {
|
|
255
|
+
type: 'string',
|
|
256
|
+
description: 'Nội dung TSX/JSX từ AI. Có thể là full file hoặc snippet.',
|
|
257
|
+
},
|
|
258
|
+
reset: {
|
|
259
|
+
type: 'boolean',
|
|
260
|
+
description: 'true = kill + restart Metro trước khi chạy. Mặc định: false.',
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
required: ['code'],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: 'stop_expo',
|
|
268
|
+
description: 'Dừng Metro server đang chạy.',
|
|
269
|
+
inputSchema: { type: 'object', properties: {} },
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: 'get_expo_status',
|
|
273
|
+
description: 'Kiểm tra trạng thái Metro server và lấy Expo URL hiện tại.',
|
|
274
|
+
inputSchema: { type: 'object', properties: {} },
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
// Register handlers
|
|
279
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
280
|
+
tools,
|
|
281
|
+
}));
|
|
282
|
+
|
|
283
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
284
|
+
const { name, arguments: args } = request.params;
|
|
285
|
+
log(`Tool call: ${name}`);
|
|
286
|
+
|
|
287
|
+
if (name === 'push_screen_and_run') return await handlePushScreen(args || {});
|
|
288
|
+
if (name === 'stop_expo') return await handleStopExpo();
|
|
289
|
+
if (name === 'get_expo_status') return await handleGetStatus();
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
293
|
+
isError: true,
|
|
294
|
+
};
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
298
|
+
|
|
299
|
+
async function main() {
|
|
300
|
+
const transport = new StdioServerTransport();
|
|
301
|
+
await server.connect(transport);
|
|
302
|
+
log('MCP server connected. Ready.');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
main().catch((err) => {
|
|
306
|
+
log('Fatal:', err);
|
|
307
|
+
process.exit(1);
|
|
308
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
> Why do I have a folder named ".expo" in my project?
|
|
2
|
+
The ".expo" folder is created when an Expo project is started using "expo start" command.
|
|
3
|
+
> What do the files contain?
|
|
4
|
+
- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
|
|
5
|
+
- "settings.json": contains the server configuration that is used to serve the application manifest.
|
|
6
|
+
> Should I commit the ".expo" folder?
|
|
7
|
+
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
|
|
8
|
+
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.
|
package/template/App.tsx
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import React, {useContext} from 'react';
|
|
2
|
+
import {ApplicationContext, Button, IconSources} from '@momo-kits/foundation';
|
|
3
|
+
import {Alert, View} from 'react-native';
|
|
4
|
+
import DemoScreen from '../../template/DemoScreen';
|
|
5
|
+
import {ItemPickerProps} from '../../template/BottomSheetPicker';
|
|
6
|
+
|
|
7
|
+
const iconData: ItemPickerProps[] = Object.keys(IconSources).map(item => {
|
|
8
|
+
return {
|
|
9
|
+
id: item,
|
|
10
|
+
value: item,
|
|
11
|
+
title: item,
|
|
12
|
+
};
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const ButtonUsage: React.FC<any> = props => {
|
|
16
|
+
const {navigator} = useContext(ApplicationContext);
|
|
17
|
+
const preview = {
|
|
18
|
+
title: {
|
|
19
|
+
title: 'Title',
|
|
20
|
+
value: [
|
|
21
|
+
{
|
|
22
|
+
value: 'Open',
|
|
23
|
+
props: {
|
|
24
|
+
onPress: () => {
|
|
25
|
+
navigator?.push({
|
|
26
|
+
options: {title: 'Test'},
|
|
27
|
+
screen: () => (
|
|
28
|
+
<View style={{flex: 1, backgroundColor: 'red'}} />
|
|
29
|
+
),
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
type: {
|
|
37
|
+
title: 'Type',
|
|
38
|
+
value: [
|
|
39
|
+
'primary',
|
|
40
|
+
'tonal',
|
|
41
|
+
'secondary',
|
|
42
|
+
'outline',
|
|
43
|
+
'text',
|
|
44
|
+
{
|
|
45
|
+
value: 'danger',
|
|
46
|
+
props: {
|
|
47
|
+
onPress: () => {
|
|
48
|
+
console.log('ABC');
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
value: 'disabled',
|
|
54
|
+
props: {
|
|
55
|
+
onPress: () => {
|
|
56
|
+
Alert.alert('ABC');
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
size: {
|
|
63
|
+
title: 'Size',
|
|
64
|
+
value: ['large', 'medium', 'small'],
|
|
65
|
+
},
|
|
66
|
+
loading: {
|
|
67
|
+
title: 'Loading',
|
|
68
|
+
value: [true],
|
|
69
|
+
},
|
|
70
|
+
iconRight: {
|
|
71
|
+
title: 'Icon right',
|
|
72
|
+
value: [
|
|
73
|
+
'https://img.mservice.com.vn/momo_app_v2/new_version/img/appx_icon/16_arrow_arrow_bold_reply_all.png',
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
iconLeft: {
|
|
77
|
+
title: 'Icon left',
|
|
78
|
+
value: [
|
|
79
|
+
{
|
|
80
|
+
value:
|
|
81
|
+
'https://img.mservice.io/momo_app_v2/new_version/img/appx_image/ic_common_logo_momo.png',
|
|
82
|
+
props: {
|
|
83
|
+
useTintColor: false,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const params = {
|
|
90
|
+
component: Button,
|
|
91
|
+
props: {
|
|
92
|
+
title: {
|
|
93
|
+
value: 'Button',
|
|
94
|
+
type: 'string',
|
|
95
|
+
},
|
|
96
|
+
type: {
|
|
97
|
+
value: 'primary',
|
|
98
|
+
type: 'enum',
|
|
99
|
+
data: [
|
|
100
|
+
'primary',
|
|
101
|
+
'secondary',
|
|
102
|
+
'tonal',
|
|
103
|
+
'danger',
|
|
104
|
+
'outline',
|
|
105
|
+
'text',
|
|
106
|
+
'disabled',
|
|
107
|
+
],
|
|
108
|
+
},
|
|
109
|
+
size: {
|
|
110
|
+
value: 'small',
|
|
111
|
+
type: 'enum',
|
|
112
|
+
data: ['small', 'medium', 'large'],
|
|
113
|
+
},
|
|
114
|
+
full: {
|
|
115
|
+
value: true,
|
|
116
|
+
type: 'bool',
|
|
117
|
+
},
|
|
118
|
+
iconRight: {
|
|
119
|
+
type: 'options',
|
|
120
|
+
data: iconData,
|
|
121
|
+
},
|
|
122
|
+
iconLeft: {
|
|
123
|
+
type: 'options',
|
|
124
|
+
data: iconData,
|
|
125
|
+
},
|
|
126
|
+
loading: {
|
|
127
|
+
value: true,
|
|
128
|
+
type: 'bool',
|
|
129
|
+
},
|
|
130
|
+
useTintColor: {
|
|
131
|
+
value: true,
|
|
132
|
+
type: 'bool',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<DemoScreen
|
|
139
|
+
component={Button}
|
|
140
|
+
preview={preview}
|
|
141
|
+
playground={params}
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export default ButtonUsage;
|