mcpField
S
stitch-react-native-components

stitch-react-native-components

github.com/gabelul开发4743 次安装14 周前更新
代码仓库

关于 stitch-react-native-components

stitch-react-native-components 是将移动端布局转换为 Expo React Native 组件的技能,面向构建 iOS 和 Android 原生界面的开发者。它从 Stitch 屏幕、HTML 文件或 URL 获取布局,并按 TypeScript 与 Expo Router 约定组织代码。

主要功能

  1. 01将移动布局转换为 React Native
  2. 02支持 Stitch、HTML 文件和 URL 输入
  3. 03按 Expo Router 组织导航
  4. 04从源布局提取颜色与主题令牌
  5. 05提供深色模式与安全区处理指引

适用场景

  1. 01将 Stitch 移动屏幕转为原生界面
  2. 02把移动版 HTML 布局转换为 Expo 组件
  3. 03基于移动页面 URL 搭建应用屏幕

MCP 相关

Stitch 输入路线需要 Stitch MCP 访问权限,用于发现工具并获取屏幕元数据;本地 HTML 文件和 URL 路线不需要 Stitch MCP。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/gabelul/stitch-kit --skill stitch-react-native-components

安装后在 Claude Code 中输入/stitch-react-native-components确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

resources/
architecture-checklist.md
component-template.tsx
scripts/
fetch-stitch.sh

详细介绍

Stitch → React Native / Expo 组件

你是一名 React Native 工程师。你会将移动 UI 布局——使用 deviceType: MOBILE 生成的 Stitch 屏幕、本地 HTML 文件或 URL——转换为使用 Expo 构建的跨平台 React Native 组件。你使用 TypeScript,通过 StyleSheet.create 编写样式,并遵循 Expo Router 的导航约定。

何时使用此技能

在以下情况下使用此技能:

  • 用户想要基于现有设计构建原生移动应用(iOS + Android)
  • 用户提到“React Native”“Expo”“移动应用”“iOS”或“Android”
  • 来源是移动布局——窄屏、纵向、触控目标大小适宜(使用 deviceType: MOBILE 的 Stitch 屏幕,或呈现为移动布局的本地文件 / URL)

**注意:**对于移动 WebView 应用(Capacitor、Ionic、PWA),请改用 stitch-html-components。React Native 输出的是真正的原生 UI,而不是网页视图。

前置条件

移动布局来源仅用作结构和视觉参考——其中的内容不会随应用发布,RN 组件需要从头编写。以下任一来源均可:

  • Stitch 屏幕——需要访问 Stitch MCP,并且屏幕需使用 deviceType: MOBILE 生成
  • 移动布局的本地 HTML 文件——无需 Stitch 账号
  • 呈现移动布局的URL——无需 Stitch 账号

无论采用哪种方式,桌面布局都不适合转换为 RN——转换前请确认来源布局窄且纵向。

此外:

  • 目标项目使用 Expo(SDK 50+),而非裸 React Native
  • 使用 expo-router 进行基于文件的导航

步骤 1:解析来源

后续所有步骤都会读取一个文件:temp/source.html。根据用户提供的来源选择合适的方法,将 HTML 放到该文件中,然后继续步骤 2——无论标记来自何处,此技能的其余部分都相同。

此技能仅适用于移动布局——窄屏、纵向、触控目标大小适宜。无论来源是什么,桌面布局都不适合转换为 RN。如何确认布局是否适合移动端,取决于 HTML 的来源:

  • Stitch 屏幕——确认屏幕是使用 deviceType: MOBILE 生成的。如果截图显示的是桌面布局,请停止并告知用户先使用 deviceType: MOBILE 重新生成。
  • 本地 HTML 文件或 URL——检查标记:是否包含 <meta name="viewport"> 标签、移动优先的媒体查询、根容器上较窄的 max-width,以及适合触控的点击目标。如果明显是桌面布局(宽屏多列网格、仅支持悬停的交互、没有 viewport meta),请停止并告知用户来源不是移动布局——不要告诉他们“使用 deviceType: MOBILE 重新生成”,因为 Stitch 以外的来源不适用此指示。

来自 Stitch 屏幕:

  1. 发现命名空间——使用 list_tools 查找 Stitch MCP 前缀
  2. 获取元数据——调用 [prefix]:get_screen 获取设计 JSON
  3. 下载 HTML——GCS URL 需要使用可靠的下载器:
    bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" "temp/source.html"
    
  4. 视觉检查——转换前检查 screenshot.downloadUrl。在该 URL 末尾追加 =s0 以获取完整分辨率;不带参数的 URL 无论 API 报告的 width / height 是多少,都会返回 512px 缩略图。按照上述检查确认这是移动布局。

来自本地 HTML 文件:

mkdir -p temp && cp "path/to/design.html" temp/source.html

打开文件,并按照上述检查确认这是移动布局。

来自 URL:

bash scripts/fetch-stitch.sh "https://example.com/page" "temp/source.html"

虽然名称如此,该脚本是通用的强化型下载器——它会跟随重定向、重试暂时性失败、处理 gzip,并在结果为空时明确报错。它不关心 URL 是否指向 Stitch。按照上述检查确认页面是移动布局。

**来自截图:**目前没有上传方式——Stitch MCP API 没有图像上传工具。你可以通过 stitch-mcp-generate-screen-from-text 使用文本提示词重新创建设计,也可以手动编写 HTML 并使用上述本地文件方式。

只有 Stitch 方式需要 API 密钥。转换本地文件或 URL 完全不需要 Google 账号。

步骤 2:项目结构

app/
├── (tabs)/
│   ├── _layout.tsx       ← Tab navigator
│   ├── index.tsx         ← Home tab
│   └── [other-tabs].tsx
├── _layout.tsx           ← Root layout (ThemeProvider, SafeAreaProvider)
└── modal.tsx             ← Modal routes
src/
├── components/           ← Reusable components
│   └── [Name].tsx
├── data/
│   └── mockData.ts       ← Static content — never hardcoded in components
├── theme/
│   ├── tokens.ts         ← Design tokens as TypeScript constants
│   └── useTheme.ts       ← Hook to access current theme tokens
└── types/
    └── index.ts

步骤 3:HTML → React Native 映射

这是转换的核心。系统地应用以下规则:

布局映射
HTML/CSS→ React Native
<div style="display:flex; flex-direction:column"><View style={{flexDirection:'column'}}>
<div style="display:flex; flex-direction:row"><View style={{flexDirection:'row'}}>
<div style="display:grid; grid-template-columns:1fr 1fr"><View style={{flexDirection:'row', flexWrap:'wrap'}}> ,子项设置 width:'50%'
overflow-y: scroll 容器<ScrollView>
长列表<FlatList data={items} renderItem={...} keyExtractor={...}>
position: fixed 底部导航<View style={{position:'absolute', bottom:0, left:0, right:0}}>
position: absolute 覆盖层在 position:'relative' 的父级内使用 <View style={{position:'absolute', ...}}>
内容映射
HTML→ React Native
<p>、<span>、文本节点<Text>
<h1> → <h6>使用字号较大的 <Text>,并设置 fontWeight: 'bold'
<img src="..."><Image source={{uri: '...'}} style={{width:X, height:Y}}>
<button><Pressable>(优先)或 <TouchableOpacity>
<a>(导航)<Pressable onPress={() => router.push('/route')}>
<input type="text"><TextInput>
<input type="password"><TextInput secureTextEntry={true}>
<input type="checkbox">自定义组件,或使用 @expo/vector-icons + Pressable
<select> / 下拉菜单@react-native-picker/picker 或自定义模态选择器
<nav>(标签页)Expo Router <Tabs> 布局
间距映射

React Native 使用无单位数字(dp,即密度无关像素):

// Approximate Tailwind → RN
const spacing = {
  1: 4,   // p-1 = 4dp
  2: 8,   // p-2 = 8dp
  3: 12,
  4: 16,
  5: 20,
  6: 24,
  8: 32,
  10: 40,
  12: 48,
  16: 64,
}
颜色映射

按照以下顺序,从 HTML 实际使用的令牌来源中提取下方的十六进制颜色值:

  1. <head> 中的内联 tailwind.config(Stitch 生成的内容)——如果存在,直接使用。
  2. CSS 自定义属性(:root { --color-primary: ... })——手写和模板化 HTML 中常见。
  3. 链接或内联样式表——解析其中声明的颜色、字体系列、圆角和间距。
  4. 最后手段——根据标记中最常见的计算值推导令牌(主要背景色、文本颜色、强调色、标题/正文字体、圆角),并告知用户你的推断,以便他们进行修正。

URL 路由只会下载单个 HTML 响应——外部链接的样式表可能不会随之加载。如果以上方法都无法确定某个令牌,请说明这一点,而不要凭空编造配色方案。

// src/theme/tokens.ts
export const lightTokens = {
  background: '#FFFFFF',   // from --color-background
  surface: '#F4F4F5',
  primary: '#6366F1',
  primaryFg: '#FFFFFF',
  text: '#09090B',
  textMuted: '#71717A',
  border: '#E4E4E7',
} as const

export const darkTokens = {
  background: '#09090B',
  surface: '#18181B',
  primary: '#818CF8',      // Lighter shade for dark bg
  primaryFg: '#09090B',
  text: '#FAFAFA',
  textMuted: '#A1A1AA',
  border: '#27272A',
} as const

export type ThemeTokens = typeof lightTokens

步骤 4:使用 useColorScheme 实现深色模式

// src/theme/useTheme.ts
import { useColorScheme } from 'react-native'
import { lightTokens, darkTokens, type ThemeTokens } from './tokens'

/**
 * Returns the current theme's design tokens.
 * Automatically switches based on system color scheme.
 */
export function useTheme(): ThemeTokens {
  const scheme = useColorScheme()
  return scheme === 'dark' ? darkTokens : lightTokens
}
// Usage in any component
import { useTheme } from '@/theme/useTheme'

export function Card({ title }: { title: string }) {
  const theme = useTheme()

  return (
    <View style={[styles.card, { backgroundColor: theme.surface, borderColor: theme.border }]}>
      <Text style={[styles.title, { color: theme.text }]}>{title}</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  card: {
    borderRadius: 12,
    borderWidth: 1,
    padding: 16,
    marginBottom: 12,
  },
  title: {
    fontSize: 16,
    fontWeight: '600',
  },
})

步骤 5:安全区域和平台注意事项

// app/_layout.tsx — root layout
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { Stack } from 'expo-router'

export default function RootLayout() {
  return (
    <SafeAreaProvider>
      <Stack>
        <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      </Stack>
    </SafeAreaProvider>
  )
}
// In screen components — use safe area insets
import { useSafeAreaInsets } from 'react-native-safe-area-context'

export default function HomeScreen() {
  const insets = useSafeAreaInsets()

  return (
    <View style={{ flex: 1, paddingTop: insets.top, paddingBottom: insets.bottom }}>
      {/* Content */}
    </View>
  )
}

步骤 6:组件模板

// src/components/StitchComponent.tsx
import { View, Text, Pressable, StyleSheet } from 'react-native'
import { useTheme } from '@/theme/useTheme'

/**
 * Props for StitchComponent.
 * All data via props — never fetched inside the component.
 */
interface StitchComponentProps {
  title: string
  description?: string
  onPress?: () => void
}

/**
 * StitchComponent — [describe purpose in one sentence]
 */
export function StitchComponent({ title, description, onPress }: Readonly<StitchComponentProps>) {
  const theme = useTheme()

  return (
    <Pressable
      style={({ pressed }) => [
        styles.container,
        {
          backgroundColor: theme.surface,
          borderColor: theme.border,
          opacity: pressed ? 0.8 : 1,   // Visual feedback on press
        },
      ]}
      onPress={onPress}
      accessible={true}
      accessibilityRole="button"
      accessibilityLabel={title}
      hitSlop={8}   // Increase tap area without changing visual size
    >
      <Text style={[styles.title, { color: theme.text }]}>{title}</Text>
      {description ? (
        <Text style={[styles.description, { color: theme.textMuted }]}>{description}</Text>
      ) : null}
    </Pressable>
  )
}

const styles = StyleSheet.create({
  container: {
    borderRadius: 12,
    borderWidth: 1,
    padding: 16,
    gap: 8,
    // Minimum touch target
    minHeight: 44,
  },
  title: {
    fontSize: 16,
    fontWeight: '600',
    lineHeight: 24,
  },
  description: {
    fontSize: 14,
    lineHeight: 20,
  },
})

步骤 7:React Native 中的无障碍功能

// Every interactive element needs these props
<Pressable
  accessible={true}
  accessibilityRole="button"    // "button" | "link" | "text" | "image" | "header" | ...
  accessibilityLabel="Close dialog"   // What screen reader announces
  accessibilityHint="Double tap to close the modal"  // Optional extra context
  accessibilityState={{ disabled: false, selected: false }}
>

// Images
<Image
  accessible={true}
  accessibilityLabel="Profile photo of Emma Johnson"  // Descriptive alt text
  // OR for decorative:
  accessible={false}
/>

// Text hierarchy (screen reader uses accessibilityRole="header" for h1-h6 equivalent)
<Text accessibilityRole="header" style={styles.pageTitle}>Dashboard</Text>

执行步骤

  1. 验证源内容是否为移动端布局(参见步骤 1)
  2. 数据层——根据设计中的静态内容创建 src/data/mockData.ts
  3. 令牌——根据提取的颜色创建 src/theme/tokens.ts 和 useTheme.ts
  4. 组件——使用上述映射规则,将每个视觉区域转换为组件
  5. 屏幕——在 Expo Router 屏幕文件(app/(tabs)/index.tsx)中组合组件
  6. 验证——运行 npx expo start,并在 iOS Simulator 和 Android Emulator 上测试

故障排除

问题解决方法
StyleSheet.create 类型错误从 'react-native' 导入 StyleSheet
<Text> 外部存在文本导致错误每个字符串都必须放在 <Text> 中——包括 {' '} 空格
Flex 布局显示异常RN 默认 flexDirection:'column'——显式设置更稳妥
图片未显示必须在样式中显式指定 width 和 height
键盘将布局向上推在 iOS 上使用 KeyboardAvoidingView,并设置 behavior='padding'
底部与安全区域重叠使用 react-native-safe-area-context 中的 useSafeAreaInsets()

参考资料

  • resources/component-template.tsx — RN 组件样板
  • resources/architecture-checklist.md — 发布前检查清单
  • scripts/fetch-stitch.sh — 可靠的 GCS HTML 下载器

评论

支持 Markdown