stitch-kit 1.5.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/AGENTS.md +139 -0
- package/CHANGELOG.md +86 -0
- package/README.md +167 -0
- package/agents/stitch-kit.md +93 -0
- package/bin/stitch-kit.mjs +430 -0
- package/docs/architecture.md +118 -0
- package/docs/color-prompt-guide.md +119 -0
- package/docs/mcp-naming-convention.md +64 -0
- package/docs/mcp-schemas/README.md +130 -0
- package/docs/mcp-schemas/apply_design_system.json +36 -0
- package/docs/mcp-schemas/create_design_system.json +78 -0
- package/docs/mcp-schemas/create_project.json +290 -0
- package/docs/mcp-schemas/delete_project.json +20 -0
- package/docs/mcp-schemas/edit_screens.json +46 -0
- package/docs/mcp-schemas/generate_screen_from_text.json +242 -0
- package/docs/mcp-schemas/generate_variants.json +77 -0
- package/docs/mcp-schemas/get_project.json +137 -0
- package/docs/mcp-schemas/get_screen.json +92 -0
- package/docs/mcp-schemas/list_design_systems.json +32 -0
- package/docs/mcp-schemas/list_projects.json +136 -0
- package/docs/mcp-schemas/list_screens.json +56 -0
- package/docs/mcp-schemas/update_design_system.json +32 -0
- package/docs/mcp-schemas/upload_screens_from_images.json +52 -0
- package/docs/prd-to-stitch-workflow.md +137 -0
- package/docs/skills-index.md +108 -0
- package/docs/tailwind-reference.md +207 -0
- package/package.json +41 -0
- package/skills/stitch-a11y/SKILL.md +428 -0
- package/skills/stitch-a11y/resources/audit-checklist.md +102 -0
- package/skills/stitch-animate/SKILL.md +371 -0
- package/skills/stitch-animate/resources/animation-patterns.md +248 -0
- package/skills/stitch-design-md/SKILL.md +215 -0
- package/skills/stitch-design-md/examples/DESIGN.md +54 -0
- package/skills/stitch-design-md/examples/usage.md +67 -0
- package/skills/stitch-design-md/scripts/fetch-stitch.sh +35 -0
- package/skills/stitch-design-system/SKILL.md +314 -0
- package/skills/stitch-design-system/resources/tokens-template.css +237 -0
- package/skills/stitch-html-components/SKILL.md +344 -0
- package/skills/stitch-html-components/resources/architecture-checklist.md +74 -0
- package/skills/stitch-html-components/scripts/fetch-stitch.sh +45 -0
- package/skills/stitch-loop/SKILL.md +183 -0
- package/skills/stitch-loop/examples/SITE.md +39 -0
- package/skills/stitch-loop/examples/next-prompt.md +24 -0
- package/skills/stitch-loop/examples/usage.md +77 -0
- package/skills/stitch-mcp-apply-design-system/SKILL.md +82 -0
- package/skills/stitch-mcp-create-design-system/SKILL.md +117 -0
- package/skills/stitch-mcp-create-project/SKILL.md +77 -0
- package/skills/stitch-mcp-delete-project/SKILL.md +62 -0
- package/skills/stitch-mcp-edit-screens/SKILL.md +121 -0
- package/skills/stitch-mcp-generate-screen-from-text/SKILL.md +102 -0
- package/skills/stitch-mcp-generate-screen-from-text/examples/desktop.md +53 -0
- package/skills/stitch-mcp-generate-screen-from-text/examples/mobile.md +71 -0
- package/skills/stitch-mcp-generate-variants/SKILL.md +124 -0
- package/skills/stitch-mcp-get-project/SKILL.md +67 -0
- package/skills/stitch-mcp-get-screen/SKILL.md +117 -0
- package/skills/stitch-mcp-get-screen/scripts/fetch-stitch.sh +35 -0
- package/skills/stitch-mcp-list-design-systems/SKILL.md +84 -0
- package/skills/stitch-mcp-list-projects/SKILL.md +77 -0
- package/skills/stitch-mcp-list-screens/SKILL.md +69 -0
- package/skills/stitch-mcp-update-design-system/SKILL.md +82 -0
- package/skills/stitch-mcp-upload-screens-from-images/SKILL.md +95 -0
- package/skills/stitch-mcp-upload-screens-from-images/scripts/encode-image.sh +43 -0
- package/skills/stitch-nextjs-components/SKILL.md +181 -0
- package/skills/stitch-nextjs-components/resources/architecture-checklist.md +65 -0
- package/skills/stitch-nextjs-components/resources/component-template.tsx +101 -0
- package/skills/stitch-nextjs-components/scripts/fetch-stitch.sh +45 -0
- package/skills/stitch-orchestrator/SKILL.md +337 -0
- package/skills/stitch-orchestrator/examples/workflow.md +173 -0
- package/skills/stitch-react-components/SKILL.md +236 -0
- package/skills/stitch-react-components/references/tailwind-to-react.md +117 -0
- package/skills/stitch-react-components/resources/architecture-checklist.md +34 -0
- package/skills/stitch-react-components/resources/component-template.tsx +35 -0
- package/skills/stitch-react-components/scripts/fetch-stitch.sh +35 -0
- package/skills/stitch-react-native-components/SKILL.md +333 -0
- package/skills/stitch-react-native-components/resources/architecture-checklist.md +74 -0
- package/skills/stitch-react-native-components/resources/component-template.tsx +104 -0
- package/skills/stitch-react-native-components/scripts/fetch-stitch.sh +45 -0
- package/skills/stitch-remotion/SKILL.md +280 -0
- package/skills/stitch-setup/SKILL.md +183 -0
- package/skills/stitch-shadcn-ui/SKILL.md +255 -0
- package/skills/stitch-skill-creator/SKILL.md +257 -0
- package/skills/stitch-skill-creator/references/output-patterns.md +71 -0
- package/skills/stitch-skill-creator/scripts/init_stitch_skill.py +229 -0
- package/skills/stitch-svelte-components/SKILL.md +282 -0
- package/skills/stitch-svelte-components/resources/architecture-checklist.md +62 -0
- package/skills/stitch-svelte-components/resources/component-template.svelte +193 -0
- package/skills/stitch-svelte-components/scripts/fetch-stitch.sh +36 -0
- package/skills/stitch-swiftui-components/SKILL.md +424 -0
- package/skills/stitch-swiftui-components/resources/architecture-checklist.md +83 -0
- package/skills/stitch-swiftui-components/resources/component-template.swift +131 -0
- package/skills/stitch-swiftui-components/resources/layout-mapping.md +155 -0
- package/skills/stitch-swiftui-components/scripts/fetch-stitch.sh +45 -0
- package/skills/stitch-ued-guide/SKILL.md +124 -0
- package/skills/stitch-ui-design-spec-generator/SKILL.md +115 -0
- package/skills/stitch-ui-design-spec-generator/examples/usage.md +79 -0
- package/skills/stitch-ui-design-variants/SKILL.md +127 -0
- package/skills/stitch-ui-prompt-architect/SKILL.md +196 -0
- package/skills/stitch-ui-prompt-architect/references/KEYWORDS.md +170 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stitch-react-native-components
|
|
3
|
+
description: Converts Stitch mobile designs into React Native / Expo components — TypeScript, StyleSheet, Expo Router, dark mode via useColorScheme, and proper touch targets. Cross-platform iOS and Android.
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- "stitch*:*"
|
|
6
|
+
- "Bash"
|
|
7
|
+
- "Read"
|
|
8
|
+
- "Write"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Stitch → React Native / Expo Components
|
|
12
|
+
|
|
13
|
+
You are a React Native engineer. You convert Stitch mobile designs (deviceType: MOBILE) into cross-platform React Native components using Expo. You work in TypeScript, use `StyleSheet.create` for styles, and follow Expo Router conventions for navigation.
|
|
14
|
+
|
|
15
|
+
## When to use this skill
|
|
16
|
+
|
|
17
|
+
Use this skill when:
|
|
18
|
+
- The user wants a **native mobile app** (iOS + Android) from a Stitch design
|
|
19
|
+
- The user mentions "React Native", "Expo", "mobile app", "iOS", "Android"
|
|
20
|
+
- The Stitch design was generated with `deviceType: MOBILE`
|
|
21
|
+
|
|
22
|
+
**Note:** For a mobile WebView app (Capacitor, Ionic, PWA), use `stitch-html-components` instead. React Native outputs actual native UI — not web views.
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- Stitch design generated with `deviceType: MOBILE` (desktop designs don't translate well to RN)
|
|
27
|
+
- Target project uses **Expo** (SDK 50+) — not bare React Native
|
|
28
|
+
- `expo-router` for file-based navigation
|
|
29
|
+
|
|
30
|
+
## Step 1: Retrieve the design
|
|
31
|
+
|
|
32
|
+
Only call this skill for **MOBILE** Stitch designs. If the screenshot shows a desktop layout, stop and tell the user to regenerate with `deviceType: MOBILE` first.
|
|
33
|
+
|
|
34
|
+
1. `list_tools` → find Stitch prefix
|
|
35
|
+
2. `[prefix]:get_screen` → fetch design JSON
|
|
36
|
+
3. Download HTML: `bash scripts/fetch-stitch.sh "[htmlCode.downloadUrl]" "temp/source.html"`
|
|
37
|
+
4. Check `screenshot.downloadUrl` — verify it's a mobile layout (narrow, vertical)
|
|
38
|
+
|
|
39
|
+
## Step 2: Project structure
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
app/
|
|
43
|
+
├── (tabs)/
|
|
44
|
+
│ ├── _layout.tsx ← Tab navigator
|
|
45
|
+
│ ├── index.tsx ← Home tab
|
|
46
|
+
│ └── [other-tabs].tsx
|
|
47
|
+
├── _layout.tsx ← Root layout (ThemeProvider, SafeAreaProvider)
|
|
48
|
+
└── modal.tsx ← Modal routes
|
|
49
|
+
src/
|
|
50
|
+
├── components/ ← Reusable components
|
|
51
|
+
│ └── [Name].tsx
|
|
52
|
+
├── data/
|
|
53
|
+
│ └── mockData.ts ← Static content — never hardcoded in components
|
|
54
|
+
├── theme/
|
|
55
|
+
│ ├── tokens.ts ← Design tokens as TypeScript constants
|
|
56
|
+
│ └── useTheme.ts ← Hook to access current theme tokens
|
|
57
|
+
└── types/
|
|
58
|
+
└── index.ts
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Step 3: The HTML → React Native mapping
|
|
62
|
+
|
|
63
|
+
This is the core of the conversion. Apply these rules systematically:
|
|
64
|
+
|
|
65
|
+
### Layout mapping
|
|
66
|
+
|
|
67
|
+
| HTML/CSS | → React Native |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `<div style="display:flex; flex-direction:column">` | `<View style={{flexDirection:'column'}}>` |
|
|
70
|
+
| `<div style="display:flex; flex-direction:row">` | `<View style={{flexDirection:'row'}}>` |
|
|
71
|
+
| `<div style="display:grid; grid-template-columns:1fr 1fr">` | `<View style={{flexDirection:'row', flexWrap:'wrap'}}>` with `width:'50%'` children |
|
|
72
|
+
| `overflow-y: scroll` container | `<ScrollView>` |
|
|
73
|
+
| Long lists | `<FlatList data={items} renderItem={...} keyExtractor={...}>` |
|
|
74
|
+
| `position: fixed` bottom nav | `<View style={{position:'absolute', bottom:0, left:0, right:0}}>` |
|
|
75
|
+
| `position: absolute` overlay | `<View style={{position:'absolute', ...}}>` inside a parent with `position:'relative'` |
|
|
76
|
+
|
|
77
|
+
### Content mapping
|
|
78
|
+
|
|
79
|
+
| HTML | → React Native |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `<p>`, `<span>`, text nodes | `<Text>` |
|
|
82
|
+
| `<h1>` → `<h6>` | `<Text>` with large font size + fontWeight: 'bold' |
|
|
83
|
+
| `<img src="...">` | `<Image source={{uri: '...'}} style={{width:X, height:Y}}>` |
|
|
84
|
+
| `<button>` | `<Pressable>` (preferred) or `<TouchableOpacity>` |
|
|
85
|
+
| `<a>` (navigation) | `<Pressable onPress={() => router.push('/route')}>` |
|
|
86
|
+
| `<input type="text">` | `<TextInput>` |
|
|
87
|
+
| `<input type="password">` | `<TextInput secureTextEntry={true}>` |
|
|
88
|
+
| `<input type="checkbox">` | Custom or `@expo/vector-icons` + `Pressable` |
|
|
89
|
+
| `<select>` / dropdown | `@react-native-picker/picker` or custom modal picker |
|
|
90
|
+
| `<nav>` (tabs) | Expo Router `<Tabs>` layout |
|
|
91
|
+
|
|
92
|
+
### Spacing mapping
|
|
93
|
+
|
|
94
|
+
React Native uses unitless numbers (dp — density-independent pixels):
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// Approximate Tailwind → RN
|
|
98
|
+
const spacing = {
|
|
99
|
+
1: 4, // p-1 = 4dp
|
|
100
|
+
2: 8, // p-2 = 8dp
|
|
101
|
+
3: 12,
|
|
102
|
+
4: 16,
|
|
103
|
+
5: 20,
|
|
104
|
+
6: 24,
|
|
105
|
+
8: 32,
|
|
106
|
+
10: 40,
|
|
107
|
+
12: 48,
|
|
108
|
+
16: 64,
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Color mapping
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// src/theme/tokens.ts — extract from Stitch Tailwind config
|
|
116
|
+
export const lightTokens = {
|
|
117
|
+
background: '#FFFFFF', // from --color-background
|
|
118
|
+
surface: '#F4F4F5',
|
|
119
|
+
primary: '#6366F1',
|
|
120
|
+
primaryFg: '#FFFFFF',
|
|
121
|
+
text: '#09090B',
|
|
122
|
+
textMuted: '#71717A',
|
|
123
|
+
border: '#E4E4E7',
|
|
124
|
+
} as const
|
|
125
|
+
|
|
126
|
+
export const darkTokens = {
|
|
127
|
+
background: '#09090B',
|
|
128
|
+
surface: '#18181B',
|
|
129
|
+
primary: '#818CF8', // Lighter shade for dark bg
|
|
130
|
+
primaryFg: '#09090B',
|
|
131
|
+
text: '#FAFAFA',
|
|
132
|
+
textMuted: '#A1A1AA',
|
|
133
|
+
border: '#27272A',
|
|
134
|
+
} as const
|
|
135
|
+
|
|
136
|
+
export type ThemeTokens = typeof lightTokens
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Step 4: Dark mode with useColorScheme
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
// src/theme/useTheme.ts
|
|
143
|
+
import { useColorScheme } from 'react-native'
|
|
144
|
+
import { lightTokens, darkTokens, type ThemeTokens } from './tokens'
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Returns the current theme's design tokens.
|
|
148
|
+
* Automatically switches based on system color scheme.
|
|
149
|
+
*/
|
|
150
|
+
export function useTheme(): ThemeTokens {
|
|
151
|
+
const scheme = useColorScheme()
|
|
152
|
+
return scheme === 'dark' ? darkTokens : lightTokens
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
// Usage in any component
|
|
158
|
+
import { useTheme } from '@/theme/useTheme'
|
|
159
|
+
|
|
160
|
+
export function Card({ title }: { title: string }) {
|
|
161
|
+
const theme = useTheme()
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<View style={[styles.card, { backgroundColor: theme.surface, borderColor: theme.border }]}>
|
|
165
|
+
<Text style={[styles.title, { color: theme.text }]}>{title}</Text>
|
|
166
|
+
</View>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const styles = StyleSheet.create({
|
|
171
|
+
card: {
|
|
172
|
+
borderRadius: 12,
|
|
173
|
+
borderWidth: 1,
|
|
174
|
+
padding: 16,
|
|
175
|
+
marginBottom: 12,
|
|
176
|
+
},
|
|
177
|
+
title: {
|
|
178
|
+
fontSize: 16,
|
|
179
|
+
fontWeight: '600',
|
|
180
|
+
},
|
|
181
|
+
})
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Step 5: Safe area and platform considerations
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
// app/_layout.tsx — root layout
|
|
188
|
+
import { SafeAreaProvider } from 'react-native-safe-area-context'
|
|
189
|
+
import { Stack } from 'expo-router'
|
|
190
|
+
|
|
191
|
+
export default function RootLayout() {
|
|
192
|
+
return (
|
|
193
|
+
<SafeAreaProvider>
|
|
194
|
+
<Stack>
|
|
195
|
+
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
|
196
|
+
</Stack>
|
|
197
|
+
</SafeAreaProvider>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
// In screen components — use safe area insets
|
|
204
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
205
|
+
|
|
206
|
+
export default function HomeScreen() {
|
|
207
|
+
const insets = useSafeAreaInsets()
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<View style={{ flex: 1, paddingTop: insets.top, paddingBottom: insets.bottom }}>
|
|
211
|
+
{/* Content */}
|
|
212
|
+
</View>
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Step 6: Component template
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
// src/components/StitchComponent.tsx
|
|
221
|
+
import { View, Text, Pressable, StyleSheet } from 'react-native'
|
|
222
|
+
import { useTheme } from '@/theme/useTheme'
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Props for StitchComponent.
|
|
226
|
+
* All data via props — never fetched inside the component.
|
|
227
|
+
*/
|
|
228
|
+
interface StitchComponentProps {
|
|
229
|
+
title: string
|
|
230
|
+
description?: string
|
|
231
|
+
onPress?: () => void
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* StitchComponent — [describe purpose in one sentence]
|
|
236
|
+
*/
|
|
237
|
+
export function StitchComponent({ title, description, onPress }: Readonly<StitchComponentProps>) {
|
|
238
|
+
const theme = useTheme()
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<Pressable
|
|
242
|
+
style={({ pressed }) => [
|
|
243
|
+
styles.container,
|
|
244
|
+
{
|
|
245
|
+
backgroundColor: theme.surface,
|
|
246
|
+
borderColor: theme.border,
|
|
247
|
+
opacity: pressed ? 0.8 : 1, // Visual feedback on press
|
|
248
|
+
},
|
|
249
|
+
]}
|
|
250
|
+
onPress={onPress}
|
|
251
|
+
accessible={true}
|
|
252
|
+
accessibilityRole="button"
|
|
253
|
+
accessibilityLabel={title}
|
|
254
|
+
hitSlop={8} // Increase tap area without changing visual size
|
|
255
|
+
>
|
|
256
|
+
<Text style={[styles.title, { color: theme.text }]}>{title}</Text>
|
|
257
|
+
{description ? (
|
|
258
|
+
<Text style={[styles.description, { color: theme.textMuted }]}>{description}</Text>
|
|
259
|
+
) : null}
|
|
260
|
+
</Pressable>
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const styles = StyleSheet.create({
|
|
265
|
+
container: {
|
|
266
|
+
borderRadius: 12,
|
|
267
|
+
borderWidth: 1,
|
|
268
|
+
padding: 16,
|
|
269
|
+
gap: 8,
|
|
270
|
+
// Minimum touch target
|
|
271
|
+
minHeight: 44,
|
|
272
|
+
},
|
|
273
|
+
title: {
|
|
274
|
+
fontSize: 16,
|
|
275
|
+
fontWeight: '600',
|
|
276
|
+
lineHeight: 24,
|
|
277
|
+
},
|
|
278
|
+
description: {
|
|
279
|
+
fontSize: 14,
|
|
280
|
+
lineHeight: 20,
|
|
281
|
+
},
|
|
282
|
+
})
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Step 7: Accessibility in React Native
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
// Every interactive element needs these props
|
|
289
|
+
<Pressable
|
|
290
|
+
accessible={true}
|
|
291
|
+
accessibilityRole="button" // "button" | "link" | "text" | "image" | "header" | ...
|
|
292
|
+
accessibilityLabel="Close dialog" // What screen reader announces
|
|
293
|
+
accessibilityHint="Double tap to close the modal" // Optional extra context
|
|
294
|
+
accessibilityState={{ disabled: false, selected: false }}
|
|
295
|
+
>
|
|
296
|
+
|
|
297
|
+
// Images
|
|
298
|
+
<Image
|
|
299
|
+
accessible={true}
|
|
300
|
+
accessibilityLabel="Profile photo of Emma Johnson" // Descriptive alt text
|
|
301
|
+
// OR for decorative:
|
|
302
|
+
accessible={false}
|
|
303
|
+
/>
|
|
304
|
+
|
|
305
|
+
// Text hierarchy (screen reader uses accessibilityRole="header" for h1-h6 equivalent)
|
|
306
|
+
<Text accessibilityRole="header" style={styles.pageTitle}>Dashboard</Text>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Execution steps
|
|
310
|
+
|
|
311
|
+
1. **Verify** it's a MOBILE Stitch design
|
|
312
|
+
2. **Data layer** — create `src/data/mockData.ts` from the static content in the design
|
|
313
|
+
3. **Tokens** — create `src/theme/tokens.ts` from extracted colors, and `useTheme.ts`
|
|
314
|
+
4. **Components** — convert each visual section to a component using the mapping rules above
|
|
315
|
+
5. **Screen** — compose components in the Expo Router screen file (`app/(tabs)/index.tsx`)
|
|
316
|
+
6. **Verify** — run `npx expo start` and test on both iOS Simulator and Android Emulator
|
|
317
|
+
|
|
318
|
+
## Troubleshooting
|
|
319
|
+
|
|
320
|
+
| Issue | Fix |
|
|
321
|
+
|-------|-----|
|
|
322
|
+
| `StyleSheet.create` type error | Import `StyleSheet` from `'react-native'` |
|
|
323
|
+
| Text outside `<Text>` error | Every string must be inside `<Text>` — even `{' '}` spaces |
|
|
324
|
+
| Flex layout looks wrong | RN defaults to `flexDirection:'column'` — explicit is safer |
|
|
325
|
+
| Image not showing | Requires explicit `width` and `height` on the style |
|
|
326
|
+
| Keyboard pushes layout up | Use `KeyboardAvoidingView` with `behavior='padding'` on iOS |
|
|
327
|
+
| Bottom safe area overlap | Use `useSafeAreaInsets()` from `react-native-safe-area-context` |
|
|
328
|
+
|
|
329
|
+
## References
|
|
330
|
+
|
|
331
|
+
- `resources/component-template.tsx` — Boilerplate RN component
|
|
332
|
+
- `resources/architecture-checklist.md` — Pre-ship checklist
|
|
333
|
+
- `scripts/fetch-stitch.sh` — Reliable GCS HTML downloader
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# React Native Components — Architecture Checklist
|
|
2
|
+
|
|
3
|
+
Run through this checklist before marking the task complete.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
- [ ] Components are in `src/components/` — one file per component
|
|
8
|
+
- [ ] Screen files are in `app/(tabs)/` or `app/` (Expo Router conventions)
|
|
9
|
+
- [ ] Static content is in `src/data/mockData.ts`, not hardcoded in JSX
|
|
10
|
+
- [ ] Shared TypeScript types are in `src/types/index.ts`
|
|
11
|
+
- [ ] Theme tokens are in `src/theme/tokens.ts` with `lightTokens` and `darkTokens`
|
|
12
|
+
- [ ] `useTheme()` hook is in `src/theme/useTheme.ts`
|
|
13
|
+
- [ ] Each component has a `Readonly<ComponentNameProps>` interface
|
|
14
|
+
|
|
15
|
+
## React Native basics
|
|
16
|
+
|
|
17
|
+
- [ ] Every string is inside a `<Text>` component — no bare strings in JSX
|
|
18
|
+
- [ ] All layout uses `StyleSheet.create` (static values) + inline objects (dynamic theme values)
|
|
19
|
+
- [ ] No hardcoded hex colors in `StyleSheet.create` — theme values go inline
|
|
20
|
+
- [ ] All `<Image>` components have explicit `width` and `height` in their style
|
|
21
|
+
- [ ] `<ScrollView>` is used instead of `overflow-y: scroll` divs
|
|
22
|
+
- [ ] Long lists use `<FlatList>` with `keyExtractor`, not `<ScrollView>` + `map()`
|
|
23
|
+
|
|
24
|
+
## Touch and interaction
|
|
25
|
+
|
|
26
|
+
- [ ] All interactive elements use `<Pressable>` (not `<TouchableOpacity>` or `<View onPress>`)
|
|
27
|
+
- [ ] `hitSlop={8}` or larger on all small tap targets
|
|
28
|
+
- [ ] Minimum touch target: 44×44 dp (`minHeight: 44, minWidth: 44`)
|
|
29
|
+
- [ ] Press visual feedback: `style={({ pressed }) => [styles.x, { opacity: pressed ? 0.8 : 1 }]}`
|
|
30
|
+
- [ ] No nested `<Pressable>` elements (causes gesture conflicts)
|
|
31
|
+
|
|
32
|
+
## Safe area and platform
|
|
33
|
+
|
|
34
|
+
- [ ] `<SafeAreaProvider>` wraps the root layout in `app/_layout.tsx`
|
|
35
|
+
- [ ] Screen-level components use `useSafeAreaInsets()` for top/bottom padding
|
|
36
|
+
- [ ] Bottom navigation doesn't overlap home indicator on iPhone
|
|
37
|
+
- [ ] Keyboard handling: `<KeyboardAvoidingView behavior="padding">` on iOS forms
|
|
38
|
+
- [ ] Platform differences (if any) are handled with `Platform.OS === 'ios'`
|
|
39
|
+
|
|
40
|
+
## TypeScript
|
|
41
|
+
|
|
42
|
+
- [ ] No `any` types
|
|
43
|
+
- [ ] Theme tokens typed as `ThemeTokens` (inferred from `typeof lightTokens`)
|
|
44
|
+
- [ ] All component props use `Readonly<>`
|
|
45
|
+
- [ ] No `@ts-ignore` without explaining why
|
|
46
|
+
|
|
47
|
+
## Dark mode
|
|
48
|
+
|
|
49
|
+
- [ ] `useTheme()` hook is used in every component that renders colors
|
|
50
|
+
- [ ] No hardcoded `'#FFFFFF'` or `'#000000'` — use `theme.background`, `theme.text`
|
|
51
|
+
- [ ] Tested: toggle device to dark mode — no elements disappear or become unreadable
|
|
52
|
+
|
|
53
|
+
## Accessibility
|
|
54
|
+
|
|
55
|
+
- [ ] All `<Pressable>` elements have `accessible={true}`, `accessibilityRole`, and `accessibilityLabel`
|
|
56
|
+
- [ ] Decorative `<Image>` elements have `accessible={false}`
|
|
57
|
+
- [ ] Content images have `accessibilityLabel` describing the image
|
|
58
|
+
- [ ] Page titles use `accessibilityRole="header"` on the heading `<Text>`
|
|
59
|
+
- [ ] Related elements grouped with `accessibilityViewIsModal` or `.accessibilityElement(children: .combine)` equivalent
|
|
60
|
+
|
|
61
|
+
## Performance
|
|
62
|
+
|
|
63
|
+
- [ ] No `console.log` in production code
|
|
64
|
+
- [ ] `StyleSheet.create` used for static styles (hoisted outside render)
|
|
65
|
+
- [ ] `FlatList` used for any list of 10+ items
|
|
66
|
+
- [ ] `useCallback` / `useMemo` used where referential equality matters (event handlers passed to children)
|
|
67
|
+
- [ ] `React.memo` on pure leaf components that receive stable props
|
|
68
|
+
|
|
69
|
+
## Expo Router
|
|
70
|
+
|
|
71
|
+
- [ ] Navigation uses `router.push('/route')` — no hardcoded `navigation.navigate`
|
|
72
|
+
- [ ] Tab layout uses Expo Router `<Tabs>` — not `react-navigation` directly
|
|
73
|
+
- [ ] Deep link paths match the file structure in `app/`
|
|
74
|
+
- [ ] `app/_layout.tsx` has `<SafeAreaProvider>` and `<Stack>` (or `<Tabs>`)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StitchComponent.tsx
|
|
3
|
+
*
|
|
4
|
+
* Generated from Stitch design via stitch-react-native-components skill.
|
|
5
|
+
* Replace "StitchComponent" with the actual component name throughout.
|
|
6
|
+
*
|
|
7
|
+
* File location: src/components/StitchComponent.tsx
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { StyleSheet, Text, Pressable, View } from 'react-native'
|
|
11
|
+
import { useTheme } from '@/theme/useTheme'
|
|
12
|
+
|
|
13
|
+
// ------------------------------------------------------------
|
|
14
|
+
// Types
|
|
15
|
+
// ------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Props for StitchComponent.
|
|
19
|
+
* All data via props — never fetched inside the component.
|
|
20
|
+
*/
|
|
21
|
+
interface StitchComponentProps {
|
|
22
|
+
/** Primary heading text */
|
|
23
|
+
title: string
|
|
24
|
+
/** Supporting description text — optional */
|
|
25
|
+
description?: string
|
|
26
|
+
/** Callback when the component is pressed */
|
|
27
|
+
onPress?: () => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ------------------------------------------------------------
|
|
31
|
+
// Component
|
|
32
|
+
// ------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* StitchComponent — [describe purpose in one sentence]
|
|
36
|
+
*/
|
|
37
|
+
export function StitchComponent({
|
|
38
|
+
title,
|
|
39
|
+
description,
|
|
40
|
+
onPress,
|
|
41
|
+
}: Readonly<StitchComponentProps>) {
|
|
42
|
+
const theme = useTheme()
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Pressable
|
|
46
|
+
// Visual press feedback via the style callback
|
|
47
|
+
style={({ pressed }) => [
|
|
48
|
+
styles.container,
|
|
49
|
+
{
|
|
50
|
+
backgroundColor: theme.surface,
|
|
51
|
+
borderColor: theme.border,
|
|
52
|
+
opacity: pressed ? 0.8 : 1,
|
|
53
|
+
},
|
|
54
|
+
]}
|
|
55
|
+
onPress={onPress}
|
|
56
|
+
// Accessibility — required for screen readers
|
|
57
|
+
accessible={true}
|
|
58
|
+
accessibilityRole="button"
|
|
59
|
+
accessibilityLabel={title}
|
|
60
|
+
// Extend tap area without changing visual size
|
|
61
|
+
hitSlop={8}
|
|
62
|
+
>
|
|
63
|
+
{/* Title */}
|
|
64
|
+
<Text style={[styles.title, { color: theme.text }]}>
|
|
65
|
+
{title}
|
|
66
|
+
</Text>
|
|
67
|
+
|
|
68
|
+
{/* Description — only render when provided */}
|
|
69
|
+
{description ? (
|
|
70
|
+
<Text style={[styles.description, { color: theme.textMuted }]}>
|
|
71
|
+
{description}
|
|
72
|
+
</Text>
|
|
73
|
+
) : null}
|
|
74
|
+
</Pressable>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ------------------------------------------------------------
|
|
79
|
+
// Styles
|
|
80
|
+
// ------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* StyleSheet.create hoists styles outside the render function.
|
|
84
|
+
* Dynamic values (from theme) go inline — static values go here.
|
|
85
|
+
*/
|
|
86
|
+
const styles = StyleSheet.create({
|
|
87
|
+
container: {
|
|
88
|
+
borderRadius: 12,
|
|
89
|
+
borderWidth: 1,
|
|
90
|
+
padding: 16,
|
|
91
|
+
gap: 8,
|
|
92
|
+
// Apple HIG / Material minimum touch target
|
|
93
|
+
minHeight: 44,
|
|
94
|
+
},
|
|
95
|
+
title: {
|
|
96
|
+
fontSize: 16,
|
|
97
|
+
fontWeight: '600',
|
|
98
|
+
lineHeight: 24,
|
|
99
|
+
},
|
|
100
|
+
description: {
|
|
101
|
+
fontSize: 14,
|
|
102
|
+
lineHeight: 20,
|
|
103
|
+
},
|
|
104
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# fetch-stitch.sh
|
|
3
|
+
# Reliably downloads Stitch HTML from Google Cloud Storage URLs.
|
|
4
|
+
# GCS URLs require redirect handling and specific security handshakes that
|
|
5
|
+
# AI fetch tools often fail on. This script handles both.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# bash scripts/fetch-stitch.sh "<url>" "<output-path>"
|
|
9
|
+
#
|
|
10
|
+
# Example:
|
|
11
|
+
# bash scripts/fetch-stitch.sh "$htmlCode_downloadUrl" "temp/source.html"
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
URL="${1:?Usage: fetch-stitch.sh <url> <output-path>}"
|
|
16
|
+
OUTPUT="${2:?Usage: fetch-stitch.sh <url> <output-path>}"
|
|
17
|
+
|
|
18
|
+
# Create output directory if it doesn't exist
|
|
19
|
+
mkdir -p "$(dirname "$OUTPUT")"
|
|
20
|
+
|
|
21
|
+
# Use curl with:
|
|
22
|
+
# -L : follow redirects (GCS uses multiple redirect hops)
|
|
23
|
+
# -A : set User-Agent to avoid bot blocking
|
|
24
|
+
# --compressed : handle gzip responses
|
|
25
|
+
# --retry 3 : retry on transient failures
|
|
26
|
+
# --retry-delay 1 : wait 1s between retries
|
|
27
|
+
# --max-time 30 : don't hang forever
|
|
28
|
+
curl -L \
|
|
29
|
+
-A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" \
|
|
30
|
+
--compressed \
|
|
31
|
+
--retry 3 \
|
|
32
|
+
--retry-delay 1 \
|
|
33
|
+
--max-time 30 \
|
|
34
|
+
--silent \
|
|
35
|
+
--show-error \
|
|
36
|
+
--output "$OUTPUT" \
|
|
37
|
+
"$URL"
|
|
38
|
+
|
|
39
|
+
# Verify the download succeeded and is not empty
|
|
40
|
+
if [ ! -s "$OUTPUT" ]; then
|
|
41
|
+
echo "Error: Downloaded file is empty. URL may be expired or invalid." >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
echo "Downloaded to: $OUTPUT ($(wc -c < "$OUTPUT") bytes)"
|