@stone-js/starters 0.8.11 → 0.8.13

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.
Files changed (78) hide show
  1. package/basic-react-declarative/package.json +5 -5
  2. package/basic-react-imperative/package.json +5 -5
  3. package/basic-react-native-declarative/App.tsx +16 -182
  4. package/basic-react-native-declarative/README.md +130 -29
  5. package/basic-react-native-declarative/app/Application.ts +17 -6
  6. package/basic-react-native-declarative/app/HomeScreen.tsx +80 -0
  7. package/basic-react-native-declarative/app/PortalMark.tsx +32 -0
  8. package/basic-react-native-declarative/app/WelcomeView.tsx +146 -0
  9. package/basic-react-native-declarative/app/theme.ts +66 -0
  10. package/basic-react-native-declarative/app.json +10 -3
  11. package/basic-react-native-declarative/assets/android-icon-background.png +0 -0
  12. package/basic-react-native-declarative/assets/android-icon-foreground.png +0 -0
  13. package/basic-react-native-declarative/assets/android-icon-monochrome.png +0 -0
  14. package/basic-react-native-declarative/assets/favicon.png +0 -0
  15. package/basic-react-native-declarative/assets/icon.png +0 -0
  16. package/basic-react-native-declarative/assets/logo-dark.png +0 -0
  17. package/basic-react-native-declarative/assets/logo-light.png +0 -0
  18. package/basic-react-native-declarative/assets/splash-icon.png +0 -0
  19. package/basic-react-native-declarative/index.ts +16 -8
  20. package/basic-react-native-declarative/metro.config.js +9 -3
  21. package/basic-react-native-declarative/package.json +9 -4
  22. package/basic-react-native-declarative/tests/Application.spec.ts +41 -43
  23. package/basic-react-native-declarative/tests/stubs/react-native.ts +42 -0
  24. package/basic-react-native-declarative/tsconfig.json +0 -1
  25. package/basic-react-native-declarative/vitest.config.ts +13 -5
  26. package/basic-react-native-imperative/App.tsx +26 -0
  27. package/basic-react-native-imperative/LICENSE +21 -0
  28. package/basic-react-native-imperative/README.md +150 -0
  29. package/basic-react-native-imperative/app/Application.ts +25 -0
  30. package/basic-react-native-imperative/app/HomeScreen.tsx +84 -0
  31. package/basic-react-native-imperative/app/PortalMark.tsx +32 -0
  32. package/basic-react-native-imperative/app/WelcomeView.tsx +146 -0
  33. package/basic-react-native-imperative/app/theme.ts +66 -0
  34. package/basic-react-native-imperative/app.json +32 -0
  35. package/basic-react-native-imperative/assets/android-icon-background.png +0 -0
  36. package/basic-react-native-imperative/assets/android-icon-foreground.png +0 -0
  37. package/basic-react-native-imperative/assets/android-icon-monochrome.png +0 -0
  38. package/basic-react-native-imperative/assets/favicon.png +0 -0
  39. package/basic-react-native-imperative/assets/icon.png +0 -0
  40. package/basic-react-native-imperative/assets/logo-dark.png +0 -0
  41. package/basic-react-native-imperative/assets/logo-light.png +0 -0
  42. package/basic-react-native-imperative/assets/splash-icon.png +0 -0
  43. package/basic-react-native-imperative/babel.config.js +16 -0
  44. package/basic-react-native-imperative/index.ts +30 -0
  45. package/basic-react-native-imperative/metro.config.js +13 -0
  46. package/basic-react-native-imperative/package.json +46 -0
  47. package/basic-react-native-imperative/tests/Application.spec.ts +63 -0
  48. package/basic-react-native-imperative/tests/stubs/react-native.ts +42 -0
  49. package/basic-react-native-imperative/tsconfig.json +15 -0
  50. package/basic-react-native-imperative/vitest.config.ts +35 -0
  51. package/basic-service-declarative/package.json +4 -4
  52. package/basic-service-imperative/package.json +4 -4
  53. package/continuum-showcase/package.json +9 -9
  54. package/full-react-declarative/app/layout/AppLayout/AppLayout.tsx +1 -1
  55. package/full-react-declarative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
  56. package/full-react-declarative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
  57. package/full-react-declarative/package.json +10 -10
  58. package/full-react-imperative/app/layout/AppLayout/AppLayout.tsx +1 -1
  59. package/full-react-imperative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
  60. package/full-react-imperative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
  61. package/full-react-imperative/app/layout/SettingsLayout/SettingsLayout.tsx +1 -1
  62. package/full-react-imperative/package.json +10 -10
  63. package/full-service-declarative/package.json +10 -10
  64. package/full-service-imperative/package.json +10 -10
  65. package/package.json +29 -15
  66. package/standard-react-declarative/package.json +9 -9
  67. package/standard-react-imperative/package.json +9 -9
  68. package/standard-service-declarative/package.json +7 -7
  69. package/standard-service-imperative/package.json +7 -7
  70. package/basic-react-native-declarative/adapter/NativeAdapter.ts +0 -108
  71. package/basic-react-native-declarative/adapter/NativeErrorHandler.ts +0 -53
  72. package/basic-react-native-declarative/adapter/NativeEventSource.ts +0 -58
  73. package/basic-react-native-declarative/adapter/RawResponseWrapper.ts +0 -30
  74. package/basic-react-native-declarative/adapter/declarations.ts +0 -40
  75. package/basic-react-native-declarative/adapter/middleware.ts +0 -73
  76. package/basic-react-native-declarative/adapter/nativeAdapterBlueprint.ts +0 -51
  77. package/basic-react-native-declarative/adapter/renderSink.ts +0 -52
  78. package/basic-react-native-declarative/app/WelcomeController.ts +0 -66
@@ -20,16 +20,16 @@
20
20
  "dependencies": {
21
21
  "react": "^19.0.0",
22
22
  "react-dom": "^19.0.0",
23
- "@stone-js/core": "^0.8.11",
24
- "@stone-js/use-react": "^0.8.11",
25
- "@stone-js/browser-adapter": "^0.8.11"
23
+ "@stone-js/core": "^0.8.13",
24
+ "@stone-js/use-react": "^0.8.13",
25
+ "@stone-js/browser-adapter": "^0.8.13"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@babel/plugin-proposal-decorators": "^7.25.9",
29
29
  "@babel/preset-env": "^7.26.9",
30
30
  "@babel/preset-typescript": "^7.27.0",
31
- "@stone-js/cli": "^0.8.11",
32
- "@stone-js/testing": "^0.8.11",
31
+ "@stone-js/cli": "^0.8.13",
32
+ "@stone-js/testing": "^0.8.13",
33
33
  "@types/node": "^24.0.7",
34
34
  "@types/react": "^19.0.7",
35
35
  "@types/react-dom": "^19.0.3",
@@ -20,16 +20,16 @@
20
20
  "dependencies": {
21
21
  "react": "^19.0.0",
22
22
  "react-dom": "^19.0.0",
23
- "@stone-js/core": "^0.8.11",
24
- "@stone-js/use-react": "^0.8.11",
25
- "@stone-js/browser-adapter": "^0.8.11"
23
+ "@stone-js/core": "^0.8.13",
24
+ "@stone-js/use-react": "^0.8.13",
25
+ "@stone-js/browser-adapter": "^0.8.13"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@babel/plugin-proposal-decorators": "^7.25.9",
29
29
  "@babel/preset-env": "^7.26.9",
30
30
  "@babel/preset-typescript": "^7.27.0",
31
- "@stone-js/cli": "^0.8.11",
32
- "@stone-js/testing": "^0.8.11",
31
+ "@stone-js/cli": "^0.8.13",
32
+ "@stone-js/testing": "^0.8.13",
33
33
  "@types/node": "^24.0.7",
34
34
  "@types/react": "^19.0.7",
35
35
  "@types/react-dom": "^19.0.3",
@@ -1,192 +1,26 @@
1
- import { stoneApp } from '@stone-js/core'
1
+ import { JSX } from 'react'
2
+ import { View } from 'react-native'
2
3
  import { StatusBar } from 'expo-status-bar'
3
- import { useEffect, useState } from 'react'
4
- import { Application } from './app/Application'
5
- import { WelcomeData, WelcomeController } from './app/WelcomeController'
6
- import { nativeEventSource } from './adapter/NativeEventSource'
7
- import { onNativeError, onNativeRender } from './adapter/renderSink'
8
- import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'
4
+ import { useStoneTheme } from './app/theme'
5
+ import { StoneNativeApp } from '@stone-js/use-react-native'
9
6
 
10
7
  /**
11
- * One platform self-check, shown as a green or red row on screen.
12
- */
13
- interface PlatformCheck {
14
- label: string
15
- passed: boolean
16
- detail: string
17
- }
18
-
19
- /**
20
- * A minimal stage-3 decorator writing into `context.metadata`: if Babel emits
21
- * the 2023-11 semantics correctly under Metro/Hermes, the value is readable
22
- * back through `Symbol.metadata` (or its `Symbol.for` fallback, the one
23
- * Stone.js core uses).
24
- */
25
- const probe: any = (_value: unknown, context: { metadata: Record<PropertyKey, unknown> }): undefined => {
26
- context.metadata.stonePoc = 'ok'
27
- }
28
-
29
- @probe
30
- class MetadataProbe {}
31
-
32
- /**
33
- * Run the runtime platform checks Stone.js depends on.
8
+ * The root component.
34
9
  *
35
- * @returns The list of checks with their outcome.
36
- */
37
- function runPlatformChecks (): PlatformCheck[] {
38
- const checks: PlatformCheck[] = []
39
-
40
- const metadataSymbol: symbol = (Symbol as any).metadata ?? Symbol.for('Symbol.metadata')
41
- const probeMetadata = (MetadataProbe as any)[metadataSymbol]
42
- checks.push({
43
- label: 'Decorators 2023-11 + Symbol.metadata',
44
- passed: probeMetadata?.stonePoc === 'ok',
45
- detail: probeMetadata?.stonePoc === 'ok' ? 'context.metadata readable' : 'metadata missing'
46
- })
47
-
48
- let urlPassed = false
49
- let urlDetail = 'URL API incomplete'
50
- try {
51
- const url = new URL('stone://app/hello/Noowow?from=poc')
52
- urlPassed = url.pathname === '/hello/Noowow' && url.searchParams.get('from') === 'poc'
53
- urlDetail = urlPassed ? 'pathname + searchParams OK' : `pathname=${String(url.pathname)}`
54
- } catch (error: any) {
55
- urlDetail = String(error?.message ?? error)
56
- }
57
- checks.push({ label: 'WHATWG URL (polyfill)', passed: urlPassed, detail: urlDetail })
58
-
59
- const encoderPassed = typeof globalThis.TextEncoder !== 'undefined' &&
60
- new TextEncoder().encode('stone').byteLength === 5
61
- checks.push({
62
- label: 'TextEncoder',
63
- passed: encoderPassed,
64
- detail: encoderPassed ? 'available' : 'missing'
65
- })
66
-
67
- return checks
68
- }
69
-
70
- /**
71
- * The proof-of-concept screen: boots the Stone.js application through the
72
- * native adapter, shows the platform checks and lets you navigate between
73
- * routes of the same domain you would deploy on any other platform.
10
+ * `StoneNativeApp` shows the screen on top of the stack, which is all a first run needs, and it
11
+ * needs nothing installed to do it. When the application grows a real navigator, this is the
12
+ * component it replaces: the stack is public state, so `@react-navigation/native-stack` drives
13
+ * itself from the same object through `useScreens()`. The README shows that wiring.
14
+ *
15
+ * @returns The application.
74
16
  */
75
- export default function App (): React.JSX.Element {
76
- const [checks, setChecks] = useState<PlatformCheck[]>([])
77
- const [payload, setPayload] = useState<WelcomeData>()
78
- const [error, setError] = useState<string>()
79
- const [booted, setBooted] = useState(false)
80
-
81
- useEffect(() => {
82
- setChecks(runPlatformChecks())
83
-
84
- onNativeRender((response) => {
85
- setError(undefined)
86
- setBooted(true)
87
- setPayload(response.content as WelcomeData)
88
- })
89
-
90
- onNativeError((err) => {
91
- setError(String(err?.message ?? err))
92
- })
93
-
94
- stoneApp({ modules: [Application, WelcomeController] })
95
- .run()
96
- .catch((err: Error) => setError(String(err?.message ?? err)))
97
- }, [])
98
-
99
- const allPassed = checks.every((check) => check.passed) && booted && error === undefined
17
+ export default function App (): JSX.Element {
18
+ const theme = useStoneTheme()
100
19
 
101
20
  return (
102
- <View style={styles.container}>
103
- <StatusBar style='light' />
104
- <ScrollView contentContainerStyle={styles.content}>
105
- <Text style={styles.title}>Stone.js × React Native</Text>
106
- <Text style={styles.subtitle}>Continuum proof of concept</Text>
107
-
108
- <View style={[styles.banner, allPassed ? styles.bannerOk : styles.bannerKo]}>
109
- <Text style={styles.bannerText}>
110
- {allPassed ? 'ALL CHECKS GREEN' : (booted ? 'CHECKS FAILING' : 'BOOTING…')}
111
- </Text>
112
- </View>
113
-
114
- {checks.map((check) => (
115
- <View key={check.label} style={styles.checkRow}>
116
- <Text style={check.passed ? styles.checkOk : styles.checkKo}>
117
- {check.passed ? '✓' : '✗'} {check.label}
118
- </Text>
119
- <Text style={styles.checkDetail}>{check.detail}</Text>
120
- </View>
121
- ))}
122
-
123
- <View style={styles.checkRow}>
124
- <Text style={booted ? styles.checkOk : styles.checkKo}>
125
- {booted ? '✓' : '…'} Kernel boot + router dispatch
126
- </Text>
127
- <Text style={styles.checkDetail}>
128
- {booted ? `matched ${String(payload?.route)}` : 'waiting for the first render'}
129
- </Text>
130
- </View>
131
-
132
- {error !== undefined && (
133
- <View style={styles.errorBox}>
134
- <Text style={styles.errorText}>{error}</Text>
135
- </View>
136
- )}
137
-
138
- {payload !== undefined && (
139
- <View style={styles.payloadBox}>
140
- <Text style={styles.payloadMessage}>{payload.message}</Text>
141
- <Text style={styles.payloadMeta}>
142
- {payload.framework.name} · {payload.framework.tagline}
143
- </Text>
144
- </View>
145
- )}
146
-
147
- <Text style={styles.sectionTitle}>Navigate the same domain</Text>
148
- <View style={styles.buttons}>
149
- <NavButton label='Home' url='stone://app/' />
150
- <NavButton label='Hello Noowow' url='stone://app/hello/Noowow' />
151
- <NavButton label='Hello Mr. Stone' url='stone://app/hello/Mr.%20Stone' />
152
- <NavButton label='Unknown route' url='stone://app/nowhere' />
153
- </View>
154
- </ScrollView>
21
+ <View style={{ flex: 1, backgroundColor: theme.ink }}>
22
+ <StatusBar style='auto' />
23
+ <StoneNativeApp />
155
24
  </View>
156
25
  )
157
26
  }
158
-
159
- /**
160
- * A navigation button emitting an intent into the native event source.
161
- */
162
- function NavButton ({ label, url }: { label: string, url: string }): React.JSX.Element {
163
- return (
164
- <Pressable style={styles.button} onPress={() => nativeEventSource.navigate(url)}>
165
- <Text style={styles.buttonText}>{label}</Text>
166
- </Pressable>
167
- )
168
- }
169
-
170
- const styles = StyleSheet.create({
171
- container: { flex: 1, backgroundColor: '#141210' },
172
- content: { padding: 24, paddingTop: 72 },
173
- title: { color: '#f5f1ea', fontSize: 26, fontWeight: '700' },
174
- subtitle: { color: '#b8b0a4', fontSize: 14, marginTop: 4, marginBottom: 20 },
175
- banner: { borderRadius: 8, paddingVertical: 10, alignItems: 'center', marginBottom: 20 },
176
- bannerOk: { backgroundColor: '#1f5130' },
177
- bannerKo: { backgroundColor: '#5e2b1e' },
178
- bannerText: { color: '#f5f1ea', fontWeight: '700', letterSpacing: 1 },
179
- checkRow: { marginBottom: 12 },
180
- checkOk: { color: '#7fd39a', fontSize: 15, fontWeight: '600' },
181
- checkKo: { color: '#e88d70', fontSize: 15, fontWeight: '600' },
182
- checkDetail: { color: '#8d8578', fontSize: 12, marginLeft: 18, marginTop: 2 },
183
- errorBox: { backgroundColor: '#3a1f18', borderRadius: 8, padding: 12, marginTop: 8 },
184
- errorText: { color: '#e88d70', fontSize: 13 },
185
- payloadBox: { backgroundColor: '#221e1a', borderRadius: 8, padding: 16, marginTop: 16 },
186
- payloadMessage: { color: '#f5f1ea', fontSize: 16, fontWeight: '600' },
187
- payloadMeta: { color: '#b8b0a4', fontSize: 12, marginTop: 6 },
188
- sectionTitle: { color: '#f5f1ea', fontSize: 16, fontWeight: '700', marginTop: 28, marginBottom: 12 },
189
- buttons: { gap: 10 },
190
- button: { backgroundColor: '#2e2823', borderRadius: 8, paddingVertical: 12, alignItems: 'center' },
191
- buttonText: { color: '#f5f1ea', fontSize: 15, fontWeight: '600' }
192
- })
@@ -1,49 +1,150 @@
1
- # Stone.js · Basic React Native starter (declarative API)
1
+ # Stone.js - Basic React Native starter
2
2
 
3
- The same Stone.js domain you deploy on Node, serverless, edge or the browser, running as a **native mobile application** with React Native and Expo.
3
+ Stone.js's basic starter to create a React Native (Expo) app using the declarative API.
4
4
 
5
- ## What this starter demonstrates
5
+ The same welcome screen the web starters show, on a phone. Same mark, same palette, same words,
6
+ because it is the same domain: `app/HomeScreen.tsx` is a page, with `handle`, `head` and `render`,
7
+ and only `render` knows it is native.
6
8
 
7
- - A Stone.js domain (`app/`) written with the declarative API: `@StoneApp`, `@Routing`, `@Controller`, `@Match`. The domain never knows it runs inside a native application.
8
- - The canonical activation paths: modules are enabled by their decorator, and the local adapter blueprint is activated through `@StoneApp(options, [blueprints])`.
9
- - Stage-3 decorators (2023-11) with `Symbol.metadata`, transformed by Babel under Metro (see `babel.config.js`: the semantics are set through `babel-preset-expo`'s `decorators` option).
10
- - The universal Stone router matching navigation intents (`stone://app/hello/:name`) exactly like it matches URLs in a browser SPA or paths behind an HTTP adapter.
11
- - A minimal native adapter (`adapter/`): it captures navigation intents, normalizes them into `IncomingBrowserEvent` and executes the render effect. It prefigures `@stone-js/react-native-adapter` and will be replaced by it once published.
12
- - The platform polyfills React Native needs (`index.ts`): the WHATWG `URL` API and, on older Hermes versions, `TextEncoder`.
9
+ ## Project Setup
13
10
 
14
- The start screen is a live self-check: decorators, URL API, TextEncoder, kernel boot and router dispatch are verified at runtime and shown as green or red rows.
11
+ ```sh
12
+ npm install
13
+ ```
15
14
 
16
- ## Run it
15
+ ### Run it
17
16
 
18
- ```bash
19
- npm install
20
- npm run ios # opens the iOS simulator directly
21
- npm start # or the interactive way: then press i (iOS) or a (Android)
17
+ ```sh
18
+ npm start # Expo dev server: press i for iOS, a for Android
19
+ npm run ios # straight to the iOS simulator
20
+ npm run android # straight to an Android emulator
22
21
  ```
23
22
 
24
- ## Develop it in a browser
23
+ ### Run it in a browser
25
24
 
26
- Faster than a simulator, with Fast Refresh, and the same code runs on a device afterwards. Two
27
- dependencies the web target needs, and one command:
25
+ The fastest loop is often a browser tab: Fast Refresh, and the same code then runs on a device
26
+ untouched. It is opt-in, because the web target needs two dependencies a native-only app has no
27
+ reason to carry.
28
28
 
29
- ```bash
29
+ ```sh
30
30
  npx expo install react-dom react-native-web
31
- npx expo start --web
31
+ npm run web
32
32
  ```
33
33
 
34
- Your routes resolve, your loaders run, your screens render. What a browser cannot do it still
35
- cannot do: `react-native-web` covers the core primitives, not every native module, so a screen
36
- built on the camera or on secure storage has to be tried on a device.
34
+ Use it for the domain, the navigation and most of the interface. Use a device before believing
35
+ anything about the parts that are actually native: `react-native-web` covers the core primitives,
36
+ not the camera, secure storage or a native gesture handler.
37
37
 
38
- ## Test it
38
+ ### Bundle for production
39
+
40
+ ```sh
41
+ npm run export # a Hermes bytecode bundle, per platform
42
+ ```
43
+
44
+ An installable build is `npx expo run:ios` or an [EAS build](https://docs.expo.dev/build/setup/):
45
+ both need a native toolchain, and they are better commands than a wrapper would be.
46
+
47
+ ### Run tests
48
+
49
+ ```sh
50
+ npm run test
51
+ npm run test:cvg
52
+ ```
39
53
 
40
- The whole chain (domain, router, adapter) is pure JavaScript, so the exact modules the application boots are also tested under Node:
54
+ The tests boot the real kernel, router, adapter and renderer under Node, send a deep link, and
55
+ assert what landed on the navigation stack. No device, no simulator, no Metro.
41
56
 
42
- ```bash
43
- npm test
57
+ ### Type-check
58
+
59
+ ```sh
44
60
  npm run typecheck
45
61
  ```
46
62
 
47
- ## What is shared with your other platforms
63
+ ## What is where
64
+
65
+ | File | What it does |
66
+ |---|---|
67
+ | `app/Application.ts` | Enables the router, the adapter and the renderer. Four decorators. |
68
+ | `app/HomeScreen.tsx` | The page answering `/`. Adding a screen is adding a file like it. |
69
+ | `app/WelcomeView.tsx` | What the screen draws, in React Native components. |
70
+ | `app/theme.ts` | The Stone.js palette, following the device's light or dark appearance. |
71
+ | `index.ts` | Polyfills, boot, and `registerRootComponent`. |
72
+ | `metro.config.js` | `withStone`: collects `app/` into `.stone/modules.ts` before Metro bundles. |
73
+
74
+ ## Nothing lists your screens
75
+
76
+ A web application never lists its pages, and this one does not either. `withStone` collects
77
+ everything under `app/` and writes `.stone/modules.ts` whenever Metro starts, so `expo start`,
78
+ `expo run:ios` and an EAS build all get it without anyone remembering to ask.
79
+
80
+ It runs when Metro starts, not while it runs: **adding** a screen to a running dev server means
81
+ restarting it. **Editing** one needs nothing, Fast Refresh was never involved.
82
+
83
+ `.stone/` is generated. It is in `.gitignore`, and there is no reason to open it.
84
+
85
+ ## Navigating
86
+
87
+ ```tsx
88
+ import { useNavigate, useGoBack } from '@stone-js/use-react-native'
89
+
90
+ const navigate = useNavigate()
91
+
92
+ navigate('/tasks/42') // push a screen
93
+ navigate('/tasks/42', 'replace') // swap the current one
94
+ navigate('/sign-in', 'reset') // start again, leaving no history
95
+ ```
96
+
97
+ Navigation goes through the router, so a screen never renders another screen itself. `useGoBack()`
98
+ returns `{ goBack, canGoBack }`: wire `goBack` to your header button and to Android's hardware back
99
+ button, and let the platform leave the application when `canGoBack` is false.
100
+
101
+ Deep links arrive the same way. This starter's scheme is `stone`, so `stone://app/?name=Ada` reaches
102
+ the page that owns `/`, with `name` readable through `event.get('name')`. Change `scheme` in
103
+ `app.json` to your own.
104
+
105
+ ## Using a native navigator
106
+
107
+ `App.tsx` renders `StoneNativeApp`, which shows the screen on top of the stack. That is the floor,
108
+ not the ceiling: it is what makes the first run work with nothing installed. The platform's
109
+ transitions, the swipe-back gesture, and a screen keeping its own state while another covers it are
110
+ things only a native navigator gives you.
111
+
112
+ The stack is public state, so a navigator drives itself from it:
113
+
114
+ ```tsx
115
+ import { useScreens } from '@stone-js/use-react-native'
116
+ import { NavigationContainer } from '@react-navigation/native'
117
+ import { createNativeStackNavigator } from '@react-navigation/native-stack'
118
+
119
+ const Stack = createNativeStackNavigator()
120
+
121
+ export default function App () {
122
+ const screens = useScreens()
123
+
124
+ return (
125
+ <NavigationContainer>
126
+ <Stack.Navigator>
127
+ {screens.map((screen) => (
128
+ <Stack.Screen key={screen.key} name={screen.key} options={{ title: screen.title }}>
129
+ {() => screen.element}
130
+ </Stack.Screen>
131
+ ))}
132
+ </Stack.Navigator>
133
+ </NavigationContainer>
134
+ )
135
+ }
136
+ ```
137
+
138
+ Nothing about your pages changes.
139
+
140
+ ## The imperative twin
141
+
142
+ The same application, written with values instead of decorators, is
143
+ [`basic-react-native-imperative`](../basic-react-native-imperative). Neither paradigm wraps the
144
+ other; both write to the same manifest.
145
+
146
+ ## Learn more
48
147
 
49
- Domain, routing, services and data loading. Not the UI components: native screens are React Native components, browser pages are DOM components.
148
+ - [Stone.js documentation](https://stonejs.dev/docs)
149
+ - [`@stone-js/use-react-native`](https://www.npmjs.com/package/@stone-js/use-react-native)
150
+ - [`@stone-js/react-native-adapter`](https://www.npmjs.com/package/@stone-js/react-native-adapter)
@@ -1,17 +1,28 @@
1
1
  import { Routing } from '@stone-js/router'
2
2
  import { LogLevel, StoneApp } from '@stone-js/core'
3
- import { nativeAdapterBlueprint } from '../adapter/nativeAdapterBlueprint'
3
+ import { ReactNative } from '@stone-js/react-native-adapter'
4
+ import { UseReactNative } from '@stone-js/use-react-native'
4
5
 
5
6
  /**
6
7
  * Application
7
8
  *
8
9
  * This is the main application entry point.
9
10
  *
10
- * @Routing() enables the Stone router: incoming navigation events are matched
11
- * against the route definitions declared by the controllers.
12
- * @StoneApp() enables the Stone application (required). Extra blueprints are
13
- * activated through its second argument: here, the native adapter.
11
+ * @Routing() is used to enable the router.
12
+ * @ReactNative() is used to enable the React Native adapter, which turns deep links and
13
+ * in-app navigation into events.
14
+ * @UseReactNative() is used to enable the React Native renderer, which turns what a page
15
+ * resolves into a screen.
16
+ * @StoneApp() is used to enable the Stone application, it is required.
17
+ *
18
+ * The web starter's Application carries the same decorators with two swapped: `@Browser()` for
19
+ * `@ReactNative()`, `@UseReact()` for `@UseReactNative()`. That is the promise, in four lines:
20
+ * the domain is written once, and what changes is the context it runs in.
21
+ *
22
+ * Screens live in their own files, one route each. See `app/HomeScreen.tsx`.
14
23
  */
15
24
  @Routing()
16
- @StoneApp({ name: 'Stone.js Native', logger: { level: LogLevel.INFO } }, [nativeAdapterBlueprint])
25
+ @ReactNative()
26
+ @UseReactNative()
27
+ @StoneApp({ logger: { level: LogLevel.INFO } })
17
28
  export class Application {}
@@ -0,0 +1,80 @@
1
+ import { JSX } from 'react'
2
+ import { WelcomeView } from './WelcomeView'
3
+ import { ILogger, Promiseable } from '@stone-js/core'
4
+ import { HeadContext, IPage, Page, PageHeadContext, PageRenderContext, ReactIncomingEvent } from '@stone-js/use-react-native'
5
+
6
+ /**
7
+ * The home screen.
8
+ *
9
+ * A page, exactly as on the web: `@Page` binds it to a route, `handle` answers, `head` names it and
10
+ * `render` draws it. The only native thing here is what `render` returns, and it returns React
11
+ * Native components instead of DOM ones.
12
+ *
13
+ * Adding a screen is adding a file like this one. Nothing lists it: `withStone` collects everything
14
+ * under `app/` before Metro bundles.
15
+ */
16
+ @Page('/')
17
+ export class HomeScreen implements IPage<ReactIncomingEvent> {
18
+ /**
19
+ * Logger is a service for logging.
20
+ */
21
+ private readonly logger: ILogger
22
+
23
+ /**
24
+ * Create a new instance of HomeScreen.
25
+ * At this point, all the dependencies are resolved and injected.
26
+ *
27
+ * @param logger - Logger service.
28
+ */
29
+ constructor ({ logger }: { logger: ILogger }) {
30
+ this.logger = logger
31
+ }
32
+
33
+ /**
34
+ * Handle incoming events.
35
+ *
36
+ * A deep link carries its parameters here: `stone://app/?name=Ada` arrives as an event, and
37
+ * `event.get` reads from it the same way an HTTP request would be read.
38
+ *
39
+ * @param event - Incoming event.
40
+ * @returns The screen's data.
41
+ */
42
+ handle (event: ReactIncomingEvent): ResponseData {
43
+ const message = `Hello ${String(event.get<string>('name', 'World'))}!`
44
+
45
+ this.logger.info(message)
46
+
47
+ return { message }
48
+ }
49
+
50
+ /**
51
+ * Set the screen's head.
52
+ *
53
+ * A phone has no meta tags, so the title is what a navigator shows in its header. The same `head`
54
+ * a web page declares, read by a different renderer.
55
+ *
56
+ * @returns The head context.
57
+ */
58
+ head ({ event }: PageHeadContext): Promiseable<HeadContext> {
59
+ return {
60
+ title: `${String(event.get<string>('name', 'World'))} · Welcome to Stone.js`,
61
+ description: 'A universal Stone.js application. Write your domain once; Stone.js is the context that runs it anywhere.'
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Render the screen.
67
+ *
68
+ * @returns The rendered screen.
69
+ */
70
+ render ({ data }: PageRenderContext<ResponseData>): JSX.Element {
71
+ return <WelcomeView message={data?.message} />
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Response data
77
+ */
78
+ export interface ResponseData {
79
+ message: string
80
+ }
@@ -0,0 +1,32 @@
1
+ import { JSX } from 'react'
2
+ import { Image, useColorScheme } from 'react-native'
3
+
4
+ /**
5
+ * Le Portail, the Stone.js mark.
6
+ *
7
+ * The same three arcs as `public/logo.svg` in the web starters: two stone segments rising, and the
8
+ * keystone lit in ember. Two files rather than one, because the segments have to contrast with the
9
+ * ground the screen paints, exactly as the brand ships a dark-ground and a light-ground Portal.
10
+ *
11
+ * Shipped as an image on purpose. Drawing it would mean `react-native-svg`, a native module, and
12
+ * a starter's first job is to run on a device the moment it is installed, with nothing to link
13
+ * and no version to match against Expo Go's.
14
+ *
15
+ * @param props.size - Side length in points.
16
+ * @returns The mark.
17
+ */
18
+ export function PortalMark ({ size = 104 }: { size?: number }): JSX.Element {
19
+ const scheme = useColorScheme()
20
+
21
+ return (
22
+ <Image
23
+ accessibilityRole='image'
24
+ accessibilityLabel='Stone.js'
25
+ source={scheme === 'light'
26
+ ? require('../assets/logo-light.png')
27
+ : require('../assets/logo-dark.png')}
28
+ style={{ width: size, height: size }}
29
+ resizeMode='contain'
30
+ />
31
+ )
32
+ }