create-proto 0.7.5 → 0.7.7
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-proto",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
4
4
|
"description": "Scaffold a new Proto prototype: `npm create proto@latest myapp`. Describe a screen, watch your prototype run natively on iPhone — designer-first, paired with Claude Code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/template/CLAUDE.md
CHANGED
|
@@ -16,7 +16,24 @@ You're the design tool inside a Prototo project. The designer prompts you in pla
|
|
|
16
16
|
- `@expo/ui/swift-ui` — `Button`, `Toggle`, `Form`, `Section`, etc.
|
|
17
17
|
- `expo-glass-effect` `GlassView` — Liquid Glass surfaces
|
|
18
18
|
|
|
19
|
-
**Prototo primitives** in `/components/proto` — small set of themed fallbacks
|
|
19
|
+
**Prototo primitives** in `/components/proto` — small set of themed fallbacks. Props cheat sheet (source is read-only; only read a file if something here doesn't match):
|
|
20
|
+
|
|
21
|
+
| Primitive | Props |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `Screen` | `scrollable?: boolean` (default true) |
|
|
24
|
+
| `Stack` | `gap?`, `padding?`, `align?: 'start'\|'center'\|'end'` (unset = full-width stretch), `style?` |
|
|
25
|
+
| `Row` | `gap?`, `align?: 'start'\|'center'\|'end'` (default 'start'), `style?` |
|
|
26
|
+
| `Text` | `size?: 'title'\|'headline'\|'body'\|'caption'\|'label'`, `color?: 'primary'\|'secondary'\|'accent'\|'destructive'`, `style?` |
|
|
27
|
+
| `Card` | `glass?: boolean`, `padding?: number` |
|
|
28
|
+
| `Button` | `label: string`, `variant?: 'primary'\|'secondary'\|'ghost'\|'destructive'`, `onPress?`, `disabled?`, `icon?`, `style?`, `textStyle?` |
|
|
29
|
+
| `Toggle` | `label: string`, `value: boolean`, `onChange?(value)` |
|
|
30
|
+
| `Slider` | `value: number`, `onChange?`, `min?`, `max?`, `step?`, `label?` |
|
|
31
|
+
| `Stepper` | `label: string`, `value: number`, `onChange(value)`, `min?`, `max?`, `step?` |
|
|
32
|
+
| `Divider` | `label?: string` |
|
|
33
|
+
| `Input` | React Native `TextInputProps` passthrough |
|
|
34
|
+
| `Modal` | `title: string`, `visible: boolean`, `onClose?` |
|
|
35
|
+
| `Lottie` | `source`, `autoPlay?`, `loop?`, `style?` |
|
|
36
|
+
`Toggle`, `Slider`, and `Stepper` render real native SwiftUI (`@expo/ui`) on iOS, tinted with the app accent. Card's `glass={true}` uses `expo-glass-effect`'s native iOS 26 material; on older iOS it falls back to a plain View.
|
|
20
37
|
|
|
21
38
|
**Prototo motion + graphics** — four subpath modules in `/components/proto` cover animation and drawing. Pick by what the prompt actually asks for:
|
|
22
39
|
|
|
@@ -32,7 +49,7 @@ Never import `react-native-ease`, `react-native-reanimated`, `lottie-react-nativ
|
|
|
32
49
|
|
|
33
50
|
## Adding a library
|
|
34
51
|
|
|
35
|
-
To add any npm package (a font, an icon set, a utility), run `proto add <package>` — never `npm install` / `pnpm add` directly. `proto add` installs through `expo install`, which picks the version that matches this project and resolves dependencies cleanly, so the project doesn't break. If the package needs native code this Prototo doesn't bundle, `proto add` will say so — that feature won't appear on the device until the Proto team ships an updated Prototo.
|
|
52
|
+
To add any npm package (a font, an icon set, a utility), run `npx proto add <package>` — never `npm install` / `pnpm add` directly (and `proto` alone isn't on PATH — always `npx proto`). `proto add` installs through `expo install`, which picks the version that matches this project and resolves dependencies cleanly, so the project doesn't break. If the package needs native code this Prototo doesn't bundle, `proto add` will say so — that feature won't appear on the device until the Proto team ships an updated Prototo.
|
|
36
53
|
|
|
37
54
|
## File layout
|
|
38
55
|
|
|
@@ -48,6 +65,29 @@ To add any npm package (a font, an icon set, a utility), run `proto add <package
|
|
|
48
65
|
A new screen `screens/Settings.tsx` needs:
|
|
49
66
|
- `app/settings.tsx` re-exporting it (`import Settings from '../screens/Settings'; export default function SettingsRoute() { return <Settings />; }`)
|
|
50
67
|
- A title set in `app/_layout.tsx`: `<Stack.Screen name="settings" options={{ title: 'Settings' }} />`
|
|
68
|
+
|
|
69
|
+
**Tabs (NativeTabs)** — exact shape for this project's pinned `expo-router` (the flat `Icon`/`Label` imports you may know do NOT exist here; they're nested under `Trigger`):
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { NativeTabs } from 'expo-router/unstable-native-tabs';
|
|
73
|
+
|
|
74
|
+
export default function Layout() {
|
|
75
|
+
return (
|
|
76
|
+
<NativeTabs>
|
|
77
|
+
<NativeTabs.Trigger name="index">
|
|
78
|
+
<NativeTabs.Trigger.Icon sf="house.fill" />
|
|
79
|
+
<NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
|
|
80
|
+
</NativeTabs.Trigger>
|
|
81
|
+
<NativeTabs.Trigger name="profile">
|
|
82
|
+
<NativeTabs.Trigger.Icon sf="person.fill" />
|
|
83
|
+
<NativeTabs.Trigger.Label>Profile</NativeTabs.Trigger.Label>
|
|
84
|
+
</NativeTabs.Trigger>
|
|
85
|
+
</NativeTabs>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Root-layout changes need a cold restart.** Swapping the navigator in `app/_layout.tsx` (Stack ↔ NativeTabs) does NOT apply via Fast Refresh, and the stale UI in a screenshot can look plausible. After editing `_layout.tsx`, call the `reload_app` MCP tool, then screenshot.
|
|
51
91
|
- Route filenames are lowercase kebab-case.
|
|
52
92
|
|
|
53
93
|
## DESIGN.md is alive
|
|
@@ -78,7 +118,7 @@ Do this especially after layout, color, typography, or spacing changes. If `prot
|
|
|
78
118
|
|
|
79
119
|
## Proto MCP tools
|
|
80
120
|
|
|
81
|
-
When the designer runs `proto start`, a local MCP server (`prototo`) connects automatically — no setup. It gives you
|
|
121
|
+
When the designer runs `proto start`, a local MCP server (`prototo`) connects automatically — no setup. It gives you four tools that close the feedback loop, so you can see what you built instead of asking the designer to relay it.
|
|
82
122
|
|
|
83
123
|
**At the start of any fix session** (the designer says something is broken, red, or not working):
|
|
84
124
|
|
|
@@ -89,6 +129,8 @@ When the designer runs `proto start`, a local MCP server (`prototo`) connects au
|
|
|
89
129
|
1. Call `compile_check` with the screen name — it type-checks the project and reports any problems in plain language. Fix anything it surfaces before moving on.
|
|
90
130
|
2. Call `get_simulator_screenshot` — it returns what the prototype actually renders right now. Inspect it for the same defects as above.
|
|
91
131
|
|
|
132
|
+
**After editing `app/_layout.tsx` or any navigator:** call `reload_app` — root-layout changes don't Fast-Refresh, and a stale screenshot looks plausible.
|
|
133
|
+
|
|
92
134
|
Never assume a screen rendered correctly — check the screenshot. Never ask the designer to describe an error you can catch with `get_metro_errors` or `compile_check`. If a tool says the Simulator isn't running (or Metro reports clean while the designer still sees an error), the designer needs to run `proto start` first.
|
|
93
135
|
|
|
94
136
|
(`get_simulator_screenshot` is the automated form of the `proto shot` loop above — prefer the tool when it's available.)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"nm":"Main Scene","h":480,"w":480,"meta":{"g":"@lottiefiles/creator@1.92.0"},"layers":[{"ty":4,"nm":"light-bot-R","sr":1,"st":0,"op":120,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,0],"t":18},{"o":{"x":0.34,"y":1.56},"i":{"x":0.64,"y":1},"s":[115,115],"t":27},{"s":[100,100],"t":34}]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[432,368]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":18},{"s":[100],"t":24}]}},"shapes":[{"ty":"rc","nm":"Rect Shape 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[96,96]}},{"ty":"fl","nm":"Fill","c":{"a":0,"k":[0.941,0.894,0.91]},"r":1,"o":{"a":0,"k":100}}],"ind":1},{"ty":4,"nm":"light-bot-L","sr":1,"st":0,"op":120,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,0],"t":18},{"o":{"x":0.34,"y":1.56},"i":{"x":0.64,"y":1},"s":[115,115],"t":27},{"s":[100,100],"t":34}]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[48,368]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":18},{"s":[100],"t":24}]}},"shapes":[{"ty":"rc","nm":"Rect Shape 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[96,96]}},{"ty":"fl","nm":"Fill","c":{"a":0,"k":[0.941,0.894,0.91]},"r":1,"o":{"a":0,"k":100}}],"ind":2},{"ty":4,"nm":"light-R-mid","sr":1,"st":0,"op":120,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,0],"t":13},{"o":{"x":0.34,"y":1.56},"i":{"x":0.64,"y":1},"s":[115,115],"t":22},{"s":[100,100],"t":29}]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[336,272]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":13},{"s":[100],"t":19}]}},"shapes":[{"ty":"rc","nm":"Rect Shape 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[96,96]}},{"ty":"fl","nm":"Fill","c":{"a":0,"k":[0.941,0.894,0.91]},"r":1,"o":{"a":0,"k":100}}],"ind":3},{"ty":4,"nm":"light-L-mid","sr":1,"st":0,"op":120,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,0],"t":13},{"o":{"x":0.34,"y":1.56},"i":{"x":0.64,"y":1},"s":[115,115],"t":22},{"s":[100,100],"t":29}]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[144,272]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":13},{"s":[100],"t":19}]}},"shapes":[{"ty":"rc","nm":"Rect Shape 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[96,96]}},{"ty":"fl","nm":"Fill","c":{"a":0,"k":[0.941,0.894,0.91]},"r":1,"o":{"a":0,"k":100}}],"ind":4},{"ty":4,"nm":"pink-far-R","sr":1,"st":0,"op":120,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,0],"t":10},{"o":{"x":0.34,"y":1.56},"i":{"x":0.64,"y":1},"s":[115,115],"t":19},{"s":[100,100],"t":26}]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[432,272]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":10},{"s":[100],"t":16}]}},"shapes":[{"ty":"rc","nm":"Rect Shape 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[96,96]}},{"ty":"fl","nm":"Fill","c":{"a":0,"k":[0.91,0.416,0.612]},"r":1,"o":{"a":0,"k":100}}],"ind":5},{"ty":4,"nm":"pink-far-L","sr":1,"st":0,"op":120,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,0],"t":10},{"o":{"x":0.34,"y":1.56},"i":{"x":0.64,"y":1},"s":[115,115],"t":19},{"s":[100,100],"t":26}]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[48,272]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":10},{"s":[100],"t":16}]}},"shapes":[{"ty":"rc","nm":"Rect Shape 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[96,96]}},{"ty":"fl","nm":"Fill","c":{"a":0,"k":[0.91,0.416,0.612]},"r":1,"o":{"a":0,"k":100}}],"ind":6},{"ty":4,"nm":"light-center","sr":1,"st":0,"op":120,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,0],"t":7},{"o":{"x":0.34,"y":1.56},"i":{"x":0.64,"y":1},"s":[115,115],"t":16},{"s":[100,100],"t":23}]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[240,176]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":7},{"s":[100],"t":13}]}},"shapes":[{"ty":"rc","nm":"Rect Shape 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[96,96]}},{"ty":"fl","nm":"Fill","c":{"a":0,"k":[0.941,0.894,0.91]},"r":1,"o":{"a":0,"k":100}}],"ind":7},{"ty":4,"nm":"pink-R-mid","sr":1,"st":0,"op":120,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,0],"t":4},{"o":{"x":0.34,"y":1.56},"i":{"x":0.64,"y":1},"s":[115,115],"t":13},{"s":[100,100],"t":20}]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[336,176]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":4},{"s":[100],"t":10}]}},"shapes":[{"ty":"rc","nm":"Rect Shape 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[96,96]}},{"ty":"fl","nm":"Fill","c":{"a":0,"k":[0.91,0.416,0.612]},"r":1,"o":{"a":0,"k":100}}],"ind":8},{"ty":4,"nm":"pink-L-mid","sr":1,"st":0,"op":120,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,0],"t":4},{"o":{"x":0.34,"y":1.56},"i":{"x":0.64,"y":1},"s":[115,115],"t":13},{"s":[100,100],"t":20}]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[144,176]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":4},{"s":[100],"t":10}]}},"shapes":[{"ty":"rc","nm":"Rect Shape 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[96,96]}},{"ty":"fl","nm":"Fill","c":{"a":0,"k":[0.91,0.416,0.612]},"r":1,"o":{"a":0,"k":100}}],"ind":9},{"ty":4,"nm":"tip","sr":1,"st":0,"op":120,"ip":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0]},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0,0],"t":0},{"o":{"x":0.34,"y":1.56},"i":{"x":0.64,"y":1},"s":[115,115],"t":9},{"s":[100,100],"t":16}]},"sk":{"a":0,"k":0},"p":{"a":0,"k":[240,80]},"r":{"a":0,"k":0},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[0],"t":0},{"s":[100],"t":6}]}},"shapes":[{"ty":"rc","nm":"Rect Shape 1","d":1,"p":{"a":0,"k":[0,0]},"r":{"a":0,"k":0},"s":{"a":0,"k":[96,96]}},{"ty":"fl","nm":"Fill","c":{"a":0,"k":[0.91,0.416,0.612]},"r":1,"o":{"a":0,"k":100}}],"ind":10}],"v":"5.7.0","fr":60,"op":120,"ip":0,"assets":[]}
|
|
@@ -4,13 +4,29 @@ import type { ReactNode } from 'react';
|
|
|
4
4
|
export type StackProps = {
|
|
5
5
|
gap?: number;
|
|
6
6
|
padding?: number;
|
|
7
|
+
// same values as Row — the asymmetry was a guessable-wrong API paper cut
|
|
8
|
+
align?: 'start' | 'center' | 'end';
|
|
7
9
|
children?: ReactNode;
|
|
8
10
|
style?: ViewProps['style'];
|
|
9
11
|
};
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
const alignMap = {
|
|
14
|
+
start: 'flex-start',
|
|
15
|
+
center: 'center',
|
|
16
|
+
end: 'flex-end',
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
// No default: an unset align keeps RN's column default (stretch — children fill
|
|
20
|
+
// the width), so existing prototypes don't reflow.
|
|
21
|
+
export function Stack({ gap = 0, padding = 0, align, style, children }: StackProps) {
|
|
12
22
|
return (
|
|
13
|
-
<View
|
|
23
|
+
<View
|
|
24
|
+
style={[
|
|
25
|
+
{ flexDirection: 'column', gap, padding },
|
|
26
|
+
align ? { alignItems: alignMap[align] } : null,
|
|
27
|
+
style,
|
|
28
|
+
]}
|
|
29
|
+
>
|
|
14
30
|
{children}
|
|
15
31
|
</View>
|
|
16
32
|
);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
1
|
+
import { useEffect, useState, type ReactNode } from 'react';
|
|
2
2
|
import { Pressable } from 'react-native';
|
|
3
3
|
import * as Clipboard from 'expo-clipboard';
|
|
4
4
|
import * as Haptics from 'expo-haptics';
|
|
@@ -6,29 +6,56 @@ import {
|
|
|
6
6
|
Animated,
|
|
7
7
|
useSharedValue,
|
|
8
8
|
useAnimatedStyle,
|
|
9
|
+
withDelay,
|
|
9
10
|
withTiming,
|
|
10
11
|
Easing,
|
|
11
12
|
} from '../components/proto/gestures';
|
|
12
|
-
import { Screen, Stack,
|
|
13
|
+
import { Screen, Stack, Text, Card, Divider, Lottie } from '../components/proto';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
// Prototo Desktop sets EXPO_PUBLIC_PROTO_DESKTOP=1 when it runs `proto start`
|
|
16
|
+
// (Metro inlines it at bundle time). In the desktop the terminal sits beside
|
|
17
|
+
// this preview with Claude already running, and the simulator clipboard never
|
|
18
|
+
// reaches the Mac, so the copy and the Copy affordance both change.
|
|
19
|
+
const IN_DESKTOP = process.env.EXPO_PUBLIC_PROTO_DESKTOP === '1';
|
|
20
|
+
|
|
21
|
+
const EXAMPLES = [
|
|
22
|
+
{
|
|
23
|
+
label: 'A whole screen',
|
|
24
|
+
prompt: 'Make me a music player home screen',
|
|
25
|
+
},
|
|
15
26
|
{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
prompt: 'Fill background to light teal covering safe area top and bottom.',
|
|
27
|
+
label: 'Native feel',
|
|
28
|
+
prompt: 'Add a liquid glass tab bar with Home, Search, Profile',
|
|
19
29
|
},
|
|
20
30
|
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
prompt: 'add a liquid glass bottom tab bar with Home, Explore, About tabs',
|
|
31
|
+
label: 'A quick change',
|
|
32
|
+
prompt: 'Make the background sunset orange',
|
|
24
33
|
},
|
|
25
34
|
];
|
|
26
35
|
|
|
27
|
-
function
|
|
36
|
+
function Enter({ delay, children }: { delay: number; children: ReactNode }) {
|
|
37
|
+
const opacity = useSharedValue(0);
|
|
38
|
+
const translateY = useSharedValue(12);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const timing = { duration: 500, easing: Easing.out(Easing.quad) };
|
|
42
|
+
opacity.value = withDelay(delay, withTiming(1, timing));
|
|
43
|
+
translateY.value = withDelay(delay, withTiming(0, timing));
|
|
44
|
+
}, [delay, opacity, translateY]);
|
|
45
|
+
|
|
46
|
+
const style = useAnimatedStyle(() => ({
|
|
47
|
+
opacity: opacity.value,
|
|
48
|
+
transform: [{ translateY: translateY.value }],
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
return <Animated.View style={style}>{children}</Animated.View>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ExampleCard({ example }: { example: (typeof EXAMPLES)[number] }) {
|
|
28
55
|
const [copied, setCopied] = useState(false);
|
|
29
56
|
|
|
30
57
|
const handleCopy = async () => {
|
|
31
|
-
await Clipboard.setStringAsync(
|
|
58
|
+
await Clipboard.setStringAsync(example.prompt);
|
|
32
59
|
await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
|
|
33
60
|
setCopied(true);
|
|
34
61
|
setTimeout(() => setCopied(false), 1500);
|
|
@@ -36,59 +63,66 @@ function TutorialCard({ step }: { step: (typeof STEPS)[number] }) {
|
|
|
36
63
|
|
|
37
64
|
return (
|
|
38
65
|
<Card padding={20}>
|
|
39
|
-
<Stack gap={
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
</
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
<Stack gap={6}>
|
|
67
|
+
<Text size="label" color="accent">
|
|
68
|
+
{example.label}
|
|
69
|
+
</Text>
|
|
70
|
+
<Text size="headline">{`"${example.prompt}"`}</Text>
|
|
71
|
+
{IN_DESKTOP ? null : (
|
|
72
|
+
<Pressable onPress={handleCopy} style={{ alignSelf: 'flex-end', marginTop: 4 }}>
|
|
73
|
+
<Text size="caption" color="accent">
|
|
74
|
+
{copied ? 'Copied' : 'Copy'}
|
|
75
|
+
</Text>
|
|
76
|
+
</Pressable>
|
|
77
|
+
)}
|
|
48
78
|
</Stack>
|
|
49
79
|
</Card>
|
|
50
80
|
);
|
|
51
81
|
}
|
|
52
82
|
|
|
53
83
|
export default function Home() {
|
|
54
|
-
const opacity = useSharedValue(0);
|
|
55
|
-
const translateY = useSharedValue(12);
|
|
56
|
-
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
opacity.value = withTiming(1, { duration: 600, easing: Easing.out(Easing.quad) });
|
|
59
|
-
translateY.value = withTiming(0, { duration: 600, easing: Easing.out(Easing.quad) });
|
|
60
|
-
}, [opacity, translateY]);
|
|
61
|
-
|
|
62
|
-
const heroStyle = useAnimatedStyle(() => ({
|
|
63
|
-
opacity: opacity.value,
|
|
64
|
-
transform: [{ translateY: translateY.value }],
|
|
65
|
-
}));
|
|
66
|
-
|
|
67
84
|
return (
|
|
68
85
|
<Screen scrollable>
|
|
69
86
|
<Stack gap={24}>
|
|
70
|
-
<
|
|
87
|
+
<Enter delay={0}>
|
|
88
|
+
<Lottie
|
|
89
|
+
source={require('../assets/lottie/logo-prototo.json')}
|
|
90
|
+
style={{ width: 56, height: 56, alignSelf: 'center' }}
|
|
91
|
+
/>
|
|
92
|
+
</Enter>
|
|
93
|
+
|
|
94
|
+
<Enter delay={80}>
|
|
71
95
|
<Card glass padding={24}>
|
|
72
96
|
<Stack gap={8}>
|
|
73
97
|
<Text size="headline">You're in.</Text>
|
|
74
98
|
<Text size="body" color="secondary">
|
|
75
|
-
{
|
|
99
|
+
{IN_DESKTOP
|
|
100
|
+
? 'Type your first prompt in the terminal beside this preview, and watch it appear here.'
|
|
101
|
+
: `In a terminal: cd {{APP_NAME}} && claude. Then paste a prompt below.`}
|
|
76
102
|
</Text>
|
|
77
103
|
</Stack>
|
|
78
104
|
</Card>
|
|
79
|
-
</
|
|
80
|
-
|
|
81
|
-
<Stack gap={12}>
|
|
82
|
-
{STEPS.map((step) => (
|
|
83
|
-
<TutorialCard key={step.n} step={step} />
|
|
84
|
-
))}
|
|
85
|
-
</Stack>
|
|
105
|
+
</Enter>
|
|
86
106
|
|
|
87
|
-
<
|
|
107
|
+
<Enter delay={160}>
|
|
108
|
+
<Stack gap={12}>
|
|
109
|
+
<Text size="label" color="secondary">
|
|
110
|
+
Try one of these
|
|
111
|
+
</Text>
|
|
112
|
+
{EXAMPLES.map((example) => (
|
|
113
|
+
<ExampleCard key={example.label} example={example} />
|
|
114
|
+
))}
|
|
115
|
+
</Stack>
|
|
116
|
+
</Enter>
|
|
88
117
|
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
118
|
+
<Enter delay={240}>
|
|
119
|
+
<Stack gap={16}>
|
|
120
|
+
<Divider />
|
|
121
|
+
<Text size="caption" color="secondary">
|
|
122
|
+
Each prompt builds on the last. Prototo reads DESIGN.md before every change.
|
|
123
|
+
</Text>
|
|
124
|
+
</Stack>
|
|
125
|
+
</Enter>
|
|
92
126
|
</Stack>
|
|
93
127
|
</Screen>
|
|
94
128
|
);
|