create-proto 0.8.3 → 0.8.4

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.8.3",
3
+ "version": "0.8.4",
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",
@@ -45,7 +45,7 @@
45
45
  "fs-extra": "^11.2.0",
46
46
  "qrcode-terminal": "^0.12.0",
47
47
  "validate-npm-package-name": "^5.0.1",
48
- "@sherizan/proto-cli": "^0.8.18"
48
+ "@sherizan/proto-cli": "^0.8.20"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/fs-extra": "^11.0.4",
@@ -3,8 +3,9 @@
3
3
  You're the design tool inside a Prototo project. The designer prompts in plain language; you build native iOS screens. The iOS Simulator is the canvas. Designers never touch files: they prompt, you write.
4
4
 
5
5
  ## Read first
6
- - `DESIGN.md` — tokens and the project's decisions. Keep it alive: update it when colors, type, spacing, shape or accent change, and add a one-line entry to its Screens section for every new screen.
6
+ - `DESIGN.md` — tokens and the project's decisions. Keep it alive: update it when colors, type, spacing, shape or accent change, and add a one-line entry to its Screens section for every new screen. Brand changes go in `proto.config.js` too (see Branding).
7
7
  - `/screens/` — what already exists.
8
+ - `screens/Components.tsx` — every component running live in this project's brand: the working reference for each prop. Delete it and `app/components.tsx` (plus Home's "See the components" button) only if the designer asks.
8
9
 
9
10
  ## Building blocks (use whatever fits)
10
11
 
@@ -43,7 +44,7 @@ You're the design tool inside a Prototo project. The designer prompts in plain l
43
44
 
44
45
  **Sensors** — `expo-sensors` (`Accelerometer`, `Gyroscope`, `DeviceMotion`, `Magnetometer`, `Barometer`, `Pedometer`). Guard with `isAvailableAsync()` and render a still fallback: the Simulator never fires motion, only a real iPhone does (via a published link in the Prototo app). Call `requestPermissionsAsync()` before `DeviceMotion` or `Pedometer`.
45
46
 
46
- **Custom** — when nothing fits, write it with React Native in `/components/shared/`. The designer's vision wins.
47
+ **Custom** — when nothing fits, write it with React Native in `/components/shared/`. The designer's vision wins. Every component you add there gets a live example, with its name as the label, in the "Your components" section of `screens/Components.tsx`, so the designer (and their team, after a remix) can see and reuse it.
47
48
 
48
49
  **Adding a package** — `npx proto add <package>` only (never `npm install` / `pnpm add`; bare `proto` isn't on PATH). It says when a package needs native code Prototo doesn't bundle.
49
50
 
@@ -84,7 +85,9 @@ export default function Layout() {
84
85
 
85
86
  ## Theme, dark mode, accessibility
86
87
  - Colors come from `useTheme()` (`theme.surface.*`, `theme.text.*`, `theme.border.*`): light/dark is automatic, so no hardcoded hex. `colorScheme: 'light' | 'dark'` in `proto.config.js` pins a scheme.
87
- - Custom brand colors, fonts or constants live **once** in `/components/shared/theme.ts` (define light + dark variants if needed). Never paste a palette into more than one screen; lift any you find.
88
+ - Spacing comes from `theme.space` (`xs` 4 · `sm` 8 · `md` 16 · `lg` 24 · `xl` 32) for every `gap`, `padding` and margin: no raw numbers, so a brand spacing change reaches every screen.
89
+ - **Branding** — accent, theme, colors, radius and spacing live in `proto.config.js`, which every component reads: `accentColor`, `theme` (`'liquidGlass'`\|`'base'`), and `tokens` overrides (`surface`, `text`, `border`, `radius`, `space`, `blur`; same keys as `useTheme()`). A brand prompt edits `proto.config.js` AND DESIGN.md in the same change, then `get_simulator_screenshot` of the Components screen to check it. Editing DESIGN.md alone changes nothing on screen.
90
+ - Brand values `proto.config.js` has no key for (a font, an extra color, constants) live **once** in `/components/shared/theme.ts` (define light + dark variants if needed). Never paste a palette into more than one screen; lift any you find.
88
91
  - Text scales with Dynamic Type: never disable it, avoid fixed heights on text containers.
89
92
  - `a11y` from `../components/proto`: tap targets ≥ `a11y.minTapTarget` (44pt), contrast ≥ `a11y.minTextContrast`, in light and dark.
90
93
 
@@ -15,6 +15,7 @@ export default function RootLayout() {
15
15
  headerLargeTitle: true,
16
16
  }}
17
17
  />
18
+ <Stack.Screen name="components" options={{ title: 'Components', headerLargeTitle: true }} />
18
19
  </Stack>
19
20
  </TouchDots>
20
21
  );
@@ -0,0 +1,5 @@
1
+ import Components from '../screens/Components';
2
+
3
+ export default function ComponentsRoute() {
4
+ return <Components />;
5
+ }
@@ -43,14 +43,22 @@ export function Screen({ scrollable = true, gradient, children }: ScreenProps) {
43
43
  ) : null;
44
44
 
45
45
  if (scrollable) {
46
+ // A hex wash paints the ScrollView's own background, fixed to the viewport, so it reaches the
47
+ // bottom edge under the home indicator and in the bounce. A wash inside the content stops at the
48
+ // content box, which iOS insets above the home indicator.
49
+ const hexWash = washColor && /^#[0-9a-f]{6}$/i.test(washColor);
46
50
  return (
47
51
  <ScrollView
48
- style={{ flex: 1, backgroundColor: theme.surface.primary }}
49
- // With a wash the content box fills the viewport so the gradient reaches the bottom edge.
50
- contentContainerStyle={{ padding, gap: padding, flexGrow: washColor ? 1 : undefined }}
52
+ style={{
53
+ flex: 1,
54
+ backgroundColor: theme.surface.primary,
55
+ experimental_backgroundImage: hexWash ? `linear-gradient(to bottom, ${washColor}00, ${washColor}1A)` : undefined,
56
+ }}
57
+ // ponytail: a named colour wash still rides inside the content box (small gap at the bottom).
58
+ contentContainerStyle={{ padding, gap: padding, flexGrow: washColor && !hexWash ? 1 : undefined }}
51
59
  contentInsetAdjustmentBehavior="automatic"
52
60
  >
53
- {wash}
61
+ {hexWash ? null : wash}
54
62
  {children}
55
63
  </ScrollView>
56
64
  );
@@ -0,0 +1,264 @@
1
+ import { useState, Fragment } from 'react';
2
+ import { SymbolView } from 'expo-symbols';
3
+ import {
4
+ Screen,
5
+ Stack,
6
+ Row,
7
+ Text,
8
+ Card,
9
+ Button,
10
+ Toggle,
11
+ Slider,
12
+ Stepper,
13
+ Picker,
14
+ DatePicker,
15
+ Menu,
16
+ Input,
17
+ Modal,
18
+ Alert,
19
+ Divider,
20
+ Lottie,
21
+ useTheme,
22
+ useAccent,
23
+ } from '../components/proto';
24
+
25
+ function Section({ title, note, children }) {
26
+ const { space } = useTheme();
27
+ const items = Array.isArray(children) ? children.filter(Boolean) : [children];
28
+ return (
29
+ <Stack gap={space.sm}>
30
+ <Text size="label" color="secondary" style={{ paddingHorizontal: space.xs }}>
31
+ {title.toUpperCase()}
32
+ </Text>
33
+ <Card glass padding={space.lg}>
34
+ <Stack gap={space.lg}>
35
+ {items.map((item, index) => (
36
+ <Fragment key={index}>
37
+ {index > 0 ? <Divider /> : null}
38
+ {item}
39
+ </Fragment>
40
+ ))}
41
+ </Stack>
42
+ </Card>
43
+ {note ? (
44
+ <Text size="caption" color="secondary" style={{ paddingHorizontal: space.xs }}>
45
+ {note}
46
+ </Text>
47
+ ) : null}
48
+ </Stack>
49
+ );
50
+ }
51
+
52
+ function Item({ name, children }) {
53
+ const { space } = useTheme();
54
+ return (
55
+ <Stack gap={space.sm}>
56
+ <Text size="caption" color="secondary">
57
+ {name}
58
+ </Text>
59
+ {children}
60
+ </Stack>
61
+ );
62
+ }
63
+
64
+ function Pair({ name, value }) {
65
+ return (
66
+ <Row align="center" style={{ justifyContent: 'space-between' }}>
67
+ <Text size="body">{name}</Text>
68
+ <Text size="body" color="secondary">
69
+ {value}
70
+ </Text>
71
+ </Row>
72
+ );
73
+ }
74
+
75
+ function Swatch({ name, color }) {
76
+ const { space } = useTheme();
77
+ return (
78
+ <Stack gap={space.xs} align="center">
79
+ <SymbolView name="circle.fill" size={40} tintColor={color} />
80
+ <Text size="caption" color="secondary">
81
+ {name}
82
+ </Text>
83
+ </Stack>
84
+ );
85
+ }
86
+
87
+ export default function Components() {
88
+ const theme = useTheme();
89
+ const accent = useAccent();
90
+ const [toggle, setToggle] = useState(true);
91
+ const [slider, setSlider] = useState(0.4);
92
+ const [count, setCount] = useState(2);
93
+ const [segment, setSegment] = useState('Day');
94
+ const [size, setSize] = useState('Medium');
95
+ const [flavour, setFlavour] = useState('Mango');
96
+ const [date, setDate] = useState(new Date());
97
+ const [text, setText] = useState('');
98
+ const [sheet, setSheet] = useState(false);
99
+ const [alert, setAlert] = useState(false);
100
+
101
+ return (
102
+ <Screen scrollable gradient>
103
+ <Stack gap={theme.space.xl}>
104
+ <Text size="body" color="secondary" style={{ paddingHorizontal: theme.space.xs }}>
105
+ Everything your agent builds with, in your brand. Name any of these in a prompt.
106
+ </Text>
107
+
108
+ <Section title="Your brand" note="Your brand lives in DESIGN.md. Ask your agent to change it and everything here follows.">
109
+ <Row style={{ justifyContent: 'space-between' }}>
110
+ <Swatch name="Accent" color={accent} />
111
+ <Swatch name="Text" color={theme.text.primary} />
112
+ <Swatch name="Secondary" color={theme.text.secondary} />
113
+ <Swatch name="Tertiary" color={theme.text.tertiary} />
114
+ <Swatch name="Destructive" color={theme.text.destructive} />
115
+ </Row>
116
+ <Stack gap={theme.space.md}>
117
+ <Row align="center" style={{ justifyContent: 'space-between' }}>
118
+ <Text size="title">Title</Text>
119
+ <Text size="caption" color="secondary">title</Text>
120
+ </Row>
121
+ <Row align="center" style={{ justifyContent: 'space-between' }}>
122
+ <Text size="headline">Headline</Text>
123
+ <Text size="caption" color="secondary">headline</Text>
124
+ </Row>
125
+ <Row align="center" style={{ justifyContent: 'space-between' }}>
126
+ <Text size="body">Body</Text>
127
+ <Text size="caption" color="secondary">body</Text>
128
+ </Row>
129
+ <Row align="center" style={{ justifyContent: 'space-between' }}>
130
+ <Text size="label">Label</Text>
131
+ <Text size="caption" color="secondary">label</Text>
132
+ </Row>
133
+ <Row align="center" style={{ justifyContent: 'space-between' }}>
134
+ <Text size="caption">Caption</Text>
135
+ <Text size="caption" color="secondary">caption</Text>
136
+ </Row>
137
+ </Stack>
138
+ <Stack gap={theme.space.md}>
139
+ <Pair name="Card corners" value={`${theme.radius.card}`} />
140
+ <Pair name="Button corners" value={`${theme.radius.button}`} />
141
+ <Pair
142
+ name="Spacing"
143
+ value={`${theme.space.xs} · ${theme.space.sm} · ${theme.space.md} · ${theme.space.lg} · ${theme.space.xl}`}
144
+ />
145
+ </Stack>
146
+ </Section>
147
+
148
+ <Section title="Your components" note="Components you build for this prototype show up here, ready to reuse.">
149
+ <Text size="body" color="secondary">
150
+ None yet
151
+ </Text>
152
+ </Section>
153
+
154
+ <Section title="Buttons">
155
+ <Item name="Button">
156
+ <Stack gap={theme.space.sm}>
157
+ <Button label="Primary" />
158
+ <Button label="Secondary" variant="secondary" />
159
+ <Button label="Destructive" variant="destructive" />
160
+ <Button label="Ghost" variant="ghost" />
161
+ </Stack>
162
+ </Item>
163
+ <Item name="Button with a symbol">
164
+ <Button label="Add to favourites" variant="secondary" systemImage="star.fill" />
165
+ </Item>
166
+ <Item name="Menu">
167
+ <Row>
168
+ <Menu
169
+ label="Options"
170
+ systemImage="ellipsis.circle"
171
+ items={[
172
+ { label: 'Share', systemImage: 'square.and.arrow.up' },
173
+ { label: 'Duplicate', systemImage: 'plus.square.on.square' },
174
+ { label: 'Delete', systemImage: 'trash', destructive: true },
175
+ ]}
176
+ />
177
+ </Row>
178
+ </Item>
179
+ </Section>
180
+
181
+ <Section title="Controls">
182
+ <Item name="Toggle">
183
+ <Toggle label="Notifications" value={toggle} onChange={setToggle} />
184
+ </Item>
185
+ <Item name="Slider">
186
+ <Slider value={slider} onChange={setSlider} />
187
+ </Item>
188
+ <Item name="Stepper">
189
+ <Stepper label={`Guests: ${count}`} value={count} onChange={setCount} min={1} max={8} />
190
+ </Item>
191
+ <Item name="DatePicker">
192
+ <DatePicker label="Check in" value={date} onChange={setDate} />
193
+ </Item>
194
+ <Item name="Input">
195
+ <Input placeholder="Your name" value={text} onChangeText={setText} />
196
+ </Item>
197
+ </Section>
198
+
199
+ <Section title="Pickers">
200
+ <Item name="Picker">
201
+ <Picker options={['Day', 'Week', 'Month']} value={segment} onChange={setSegment} />
202
+ </Item>
203
+ <Item name="Picker menu">
204
+ <Picker variant="menu" label="Size" options={['Small', 'Medium', 'Large']} value={size} onChange={setSize} />
205
+ </Item>
206
+ <Item name="Picker wheel">
207
+ <Picker variant="wheel" options={['Mango', 'Lychee', 'Pandan', 'Durian']} value={flavour} onChange={setFlavour} />
208
+ </Item>
209
+ </Section>
210
+
211
+ <Section title="Overlays">
212
+ <Item name="Modal">
213
+ <Button label="Open a sheet" variant="secondary" onPress={() => setSheet(true)} />
214
+ </Item>
215
+ <Item name="Alert">
216
+ <Button label="Show an alert" variant="secondary" onPress={() => setAlert(true)} />
217
+ </Item>
218
+ </Section>
219
+
220
+ <Section title="Layout">
221
+ <Item name="Card">
222
+ <Card>
223
+ <Text size="body">A plain card</Text>
224
+ </Card>
225
+ </Item>
226
+ <Item name="Row">
227
+ <Row gap={theme.space.md}>
228
+ <Text size="body">One</Text>
229
+ <Text size="body">Two</Text>
230
+ <Text size="body">Three</Text>
231
+ </Row>
232
+ </Item>
233
+ <Item name="Divider">
234
+ <Divider label="or" />
235
+ </Item>
236
+ </Section>
237
+
238
+ <Section title="Motion">
239
+ <Item name="Lottie">
240
+ <Lottie source={require('../assets/lottie/logo-prototo.json')} style={{ width: 64, height: 64 }} />
241
+ </Item>
242
+ </Section>
243
+ </Stack>
244
+
245
+ <Modal title="A bottom sheet" visible={sheet} onClose={() => setSheet(false)}>
246
+ <Text size="body" color="secondary">
247
+ It fits its content. Swipe down to close.
248
+ </Text>
249
+ <Button label="Done" onPress={() => setSheet(false)} />
250
+ </Modal>
251
+
252
+ <Alert
253
+ title="Delete this draft?"
254
+ message="You can't undo this."
255
+ visible={alert}
256
+ onClose={() => setAlert(false)}
257
+ actions={[
258
+ { label: 'Cancel', cancel: true },
259
+ { label: 'Delete', destructive: true },
260
+ ]}
261
+ />
262
+ </Screen>
263
+ );
264
+ }
@@ -1,5 +1,6 @@
1
1
  import { useEffect, type ReactNode } from 'react';
2
2
  import { SymbolView } from 'expo-symbols';
3
+ import { router } from 'expo-router';
3
4
  import * as Clipboard from 'expo-clipboard';
4
5
  import * as Haptics from 'expo-haptics';
5
6
  import {
@@ -10,7 +11,7 @@ import {
10
11
  withTiming,
11
12
  Easing,
12
13
  } from '../components/proto/gestures';
13
- import { Screen, Stack, Row, Text, Card, Button, Divider, Lottie, useAccent } from '../components/proto';
14
+ import { Screen, Stack, Row, Text, Card, Button, Divider, Lottie, useTheme, useAccent } from '../components/proto';
14
15
 
15
16
  // Prototo Desktop sets EXPO_PUBLIC_PROTO_DESKTOP=1 when it runs `proto start`
16
17
  // (Metro inlines it at bundle time). In the desktop the terminal sits beside
@@ -44,6 +45,7 @@ function Enter({ delay, children }: { delay: number; children: ReactNode }) {
44
45
 
45
46
  function ExampleCard({ example }: { example: (typeof EXAMPLES)[number] }) {
46
47
  const accent = useAccent();
48
+ const { space } = useTheme();
47
49
 
48
50
  const handleCopy = async () => {
49
51
  await Clipboard.setStringAsync(example.prompt);
@@ -51,10 +53,10 @@ function ExampleCard({ example }: { example: (typeof EXAMPLES)[number] }) {
51
53
  };
52
54
 
53
55
  return (
54
- <Card glass padding={16}>
55
- <Row gap={14} align="center">
56
+ <Card glass padding={space.md}>
57
+ <Row gap={space.md} align="center">
56
58
  <SymbolView name="text.bubble" size={22} tintColor={accent} />
57
- <Stack gap={2} style={{ flex: 1 }}>
59
+ <Stack gap={space.xs} style={{ flex: 1 }}>
58
60
  <Text size="label" color="accent">
59
61
  {example.label}
60
62
  </Text>
@@ -71,22 +73,23 @@ function ExampleCard({ example }: { example: (typeof EXAMPLES)[number] }) {
71
73
  }
72
74
 
73
75
  export default function Home() {
76
+ const { space } = useTheme();
74
77
  return (
75
78
  <Screen scrollable gradient>
76
- <Stack gap={24}>
79
+ <Stack gap={space.lg}>
77
80
  <Enter delay={0}>
78
- <Card glass padding={24}>
79
- <Row gap={16} align="center">
81
+ <Card glass padding={space.lg}>
82
+ <Row gap={space.md} align="center">
80
83
  <Lottie
81
84
  source={require('../assets/lottie/logo-prototo.json')}
82
85
  style={{ width: 48, height: 48 }}
83
86
  />
84
- <Stack gap={4} style={{ flex: 1 }}>
87
+ <Stack gap={space.xs} style={{ flex: 1 }}>
85
88
  <Text size="headline">You're in.</Text>
86
89
  <Text size="body" color="secondary">
87
90
  {IN_DESKTOP
88
91
  ? 'Describe a screen in the terminal beside this preview. It appears here as it builds.'
89
- : `In a terminal: cd {{APP_NAME}} && claude (or codex). Then paste a prompt below.`}
92
+ : 'Copy a prompt below, or just say what you want.'}
90
93
  </Text>
91
94
  </Stack>
92
95
  </Row>
@@ -94,7 +97,7 @@ export default function Home() {
94
97
  </Enter>
95
98
 
96
99
  <Enter delay={120}>
97
- <Stack gap={10}>
100
+ <Stack gap={space.sm}>
98
101
  <Text size="label" color="secondary">
99
102
  Try one of these
100
103
  </Text>
@@ -105,11 +108,19 @@ export default function Home() {
105
108
  </Enter>
106
109
 
107
110
  <Enter delay={240}>
108
- <Stack gap={12}>
109
- <Divider />
110
- <Text size="caption" color="secondary">
111
- Each prompt builds on the last. Prototo reads DESIGN.md before every change.
112
- </Text>
111
+ <Stack gap={space.lg}>
112
+ <Stack gap={space.sm}>
113
+ <Divider />
114
+ <Text size="caption" color="secondary">
115
+ Each prompt builds on the last. Your design system lives in DESIGN.md, and your agent reads it before every change.
116
+ </Text>
117
+ </Stack>
118
+ <Button
119
+ label="See the components"
120
+ variant="secondary"
121
+ systemImage="square.grid.2x2"
122
+ onPress={() => router.push('/components')}
123
+ />
113
124
  </Stack>
114
125
  </Enter>
115
126
  </Stack>