create-octane-xplat 0.9.0 → 0.10.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/README.md +18 -3
- package/index.mjs +106 -16
- package/package.json +8 -1
- package/scaffold.mjs +531 -0
- package/template/.agents/skills/xplat/SKILL.md +107 -38
- package/template/.agents/skills/xplat/references/components.md +149 -96
- package/template/.agents/skills/xplat/references/navigation.md +81 -31
- package/template/.agents/skills/xplat/references/platform.md +74 -39
- package/template/.agents/skills/xplat/references/styling.md +80 -27
- package/template/package.json +5 -5
- package/template/pnpm-workspace.yaml +21 -7
- package/template/src/App.tsrx +5 -5
- package/template/src/style.css +24 -6
- package/template/vite.config.native.mts +3 -2
|
@@ -1,98 +1,151 @@
|
|
|
1
1
|
# Components — the vocabulary
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
3
|
+
Use shared components for screens that work across targets. Put these examples
|
|
4
|
+
in `.tsrx` files; style their classes in the app's CSS.
|
|
5
|
+
|
|
6
|
+
```tsrx
|
|
7
|
+
import { VStack, HStack, Text, Pressable } from '@octane-xplat/ui'
|
|
8
|
+
|
|
9
|
+
export function Welcome() {
|
|
10
|
+
return <VStack className="welcome">
|
|
11
|
+
<Text>Plan your next trip</Text>
|
|
12
|
+
<HStack><Pressable onPress={() => console.log('Start planning')}>
|
|
13
|
+
<Text>Start planning</Text>
|
|
14
|
+
</Pressable></HStack>
|
|
15
|
+
</VStack>
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Shared component inventory
|
|
20
|
+
|
|
21
|
+
| Need | Component(s) |
|
|
22
|
+
| --------------------- | ----------------------------------------------------------------------------- |
|
|
23
|
+
| Layout | `View`, `VStack`, `HStack`, `Grid`, `Stack`, `Absolute`, `Spacer`, `SafeArea` |
|
|
24
|
+
| Text | `Text`, `Heading`, `RichText`, `RichTextSpan` |
|
|
25
|
+
| Tap | `Pressable`, `Button`, `Link`, `NavLink` |
|
|
26
|
+
| Input | `TextInput`, `TextArea`, `Switch`, `Slider` |
|
|
27
|
+
| Scrolling | `ScrollableArea`, `VirtualList` |
|
|
28
|
+
| Media | `Image`, `Icon` |
|
|
29
|
+
| Feedback | `Spinner`, `Meter`, `showToast` |
|
|
30
|
+
| Overlays | `BottomSheet`, `Overlay`, `Popover`, `Drawer`, `HoverCard`, `Tooltip` |
|
|
31
|
+
| Shells | `Screen`, `Tabs` |
|
|
32
|
+
| Theme and measurement | `useColorScheme`, `useSafeAreaInsets`, `useMeasure` |
|
|
33
|
+
|
|
34
|
+
`ScrollableArea` scrolls a bounded set of children. For a long list, use
|
|
35
|
+
`VirtualList` so the app does not mount every row at once.
|
|
36
|
+
|
|
37
|
+
```tsrx
|
|
38
|
+
import { ScrollableArea, VirtualList, Text } from '@octane-xplat/ui'
|
|
39
|
+
|
|
40
|
+
const destinations = [{ id: 'oslo', name: 'Oslo' }, { id: 'rome', name: 'Rome' }]
|
|
41
|
+
|
|
42
|
+
export function Destinations() {
|
|
43
|
+
return <>
|
|
44
|
+
<ScrollableArea><Text>Choose your next destination</Text></ScrollableArea>
|
|
45
|
+
<VirtualList items={destinations} keyExtractor={(item) => item.id}
|
|
46
|
+
renderItem={(item) => <Text>{item.name}</Text>} />
|
|
47
|
+
</>
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Text fields keep the platform's keyboard, caret, selection, and input methods.
|
|
52
|
+
Use `onChange` to store the new value. `KeyboardAvoiding` helps keep the field
|
|
53
|
+
visible when the keyboard opens.
|
|
54
|
+
|
|
55
|
+
```tsrx
|
|
56
|
+
import { useState } from 'octane'
|
|
57
|
+
import { KeyboardAvoiding, TextInput } from '@octane-xplat/ui'
|
|
58
|
+
|
|
59
|
+
export function TravelerName() {
|
|
60
|
+
const [name, setName] = useState('')
|
|
61
|
+
return <KeyboardAvoiding>
|
|
62
|
+
<TextInput accessibilityLabel="Traveler name" placeholder="Your name"
|
|
63
|
+
value={name} onChange={setName} />
|
|
64
|
+
</KeyboardAvoiding>
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`TextArea` supports multiline text. Its submit shortcut is Cmd/Ctrl+Enter on
|
|
69
|
+
web; native submits with `returnKeyType="done"` or `"send"`.
|
|
70
|
+
|
|
71
|
+
```tsrx
|
|
72
|
+
import { useState } from 'octane'
|
|
73
|
+
import { TextArea } from '@octane-xplat/ui'
|
|
74
|
+
|
|
75
|
+
export function TravelNotes() {
|
|
76
|
+
const [notes, setNotes] = useState('')
|
|
77
|
+
return <TextArea value={notes} onChange={setNotes} returnKeyType="done"
|
|
78
|
+
onSubmit={() => console.log(notes)} accessibilityLabel="Travel notes" />
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`BottomSheet` opens above the current screen. Keep its open state in your
|
|
83
|
+
component and give it a label that explains its purpose.
|
|
84
|
+
|
|
85
|
+
```tsrx
|
|
86
|
+
import { useState } from 'octane'
|
|
87
|
+
import { BottomSheet, Pressable, Text } from '@octane-xplat/ui'
|
|
88
|
+
|
|
89
|
+
export function TripHelp() {
|
|
90
|
+
const [open, setOpen] = useState(false)
|
|
91
|
+
return <>
|
|
92
|
+
<Pressable onPress={() => setOpen(true)}><Text>Trip help</Text></Pressable>
|
|
93
|
+
<BottomSheet label="Trip help" isOpen={open} onOpenChange={setOpen}>
|
|
94
|
+
<Text>You can change your destination later.</Text>
|
|
95
|
+
</BottomSheet>
|
|
96
|
+
</>
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`RichTextSpan` gives individual text runs their own style or press handler.
|
|
101
|
+
Native uses formatted text runs for these spans.
|
|
102
|
+
|
|
103
|
+
```tsrx
|
|
104
|
+
import { RichText, RichTextSpan } from '@octane-xplat/ui'
|
|
105
|
+
|
|
106
|
+
export function TripStatus() {
|
|
107
|
+
return <RichText>
|
|
108
|
+
<RichTextSpan text="Your trip is " />
|
|
109
|
+
<RichTextSpan text="ready" className="status-ready"
|
|
110
|
+
onPress={() => console.log('Show trip')} />
|
|
111
|
+
</RichText>
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Platform widgets
|
|
116
|
+
|
|
117
|
+
Choose platform widgets when you want the OS's own controls. Import them only
|
|
118
|
+
in the matching platform file. This iOS example belongs in `Preference.ios.tsrx`:
|
|
119
|
+
|
|
120
|
+
```tsrx
|
|
121
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
122
|
+
import { useState } from 'octane'
|
|
123
|
+
import { UISwitch } from '@octane-xplat/ui/ios'
|
|
124
|
+
|
|
125
|
+
export function Preference() {
|
|
126
|
+
const [checked, setChecked] = useState(false)
|
|
127
|
+
return <UISwitch checked={checked} onCheckedChange={setChecked} />
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The Android equivalent belongs in `Preference.android.tsrx`:
|
|
132
|
+
|
|
133
|
+
```tsrx
|
|
134
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
135
|
+
import { useState } from 'octane'
|
|
136
|
+
import { MaterialSwitch } from '@octane-xplat/ui/android'
|
|
137
|
+
|
|
138
|
+
export function Preference() {
|
|
139
|
+
const [checked, setChecked] = useState(false)
|
|
140
|
+
return <MaterialSwitch checked={checked} onCheckedChange={setChecked} />
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
| Platform | Additional widget names |
|
|
145
|
+
| -------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
146
|
+
| iOS | `UISlider`, `UIActivityIndicatorView`, `UITableView`, `UITabBar`, `UIModal`, `SideDrawer`, `LiquidGlass`, `LiquidGlassContainer` |
|
|
147
|
+
| Android | `SeekBar`, `CircularProgressIndicator`, `RecyclerView`, `BottomNavigationView`, `MaterialDialog`, `DrawerLayout` |
|
|
148
|
+
|
|
149
|
+
Keep platform lists outside other scrolling containers. Use a layout container
|
|
150
|
+
around the list; see the [components guide](https://octane-xplat.goddardai.org/components)
|
|
151
|
+
for overlay, hover, tab, and accessibility examples and their platform limits.
|
|
@@ -1,33 +1,83 @@
|
|
|
1
1
|
# Navigation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
3
|
+
Register screens once during app startup. A route names a screen and a stack;
|
|
4
|
+
web turns it into a URL, while native presents the screen in the app's navigation.
|
|
5
|
+
Put components in `.tsrx` files.
|
|
6
|
+
|
|
7
|
+
```tsrx
|
|
8
|
+
import { defineRoutes, registerRoutes, Text } from '@octane-xplat/ui'
|
|
9
|
+
|
|
10
|
+
function Home() { return <Text>Home</Text> }
|
|
11
|
+
function Settings() { return <Text>Settings</Text> }
|
|
12
|
+
|
|
13
|
+
registerRoutes(defineRoutes([
|
|
14
|
+
{ path: '/', screen: Home },
|
|
15
|
+
{ path: '/settings', screen: Settings },
|
|
16
|
+
]))
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
After registering `settings`, use `NavLink` for a screen link or `pushRoute`
|
|
20
|
+
in an event handler. `Link` accepts a URL, including an external website.
|
|
21
|
+
|
|
22
|
+
```tsrx
|
|
23
|
+
import { HStack, Link, NavLink, Pressable, Text, pushRoute } from '@octane-xplat/ui'
|
|
24
|
+
|
|
25
|
+
export function SettingsLinks() {
|
|
26
|
+
const settings = { stack: 'root', name: 'settings', params: {} }
|
|
27
|
+
return <HStack>
|
|
28
|
+
<NavLink route={settings}><Text>Settings</Text></NavLink>
|
|
29
|
+
<Pressable onPress={() => pushRoute(settings)}><Text>Open settings</Text></Pressable>
|
|
30
|
+
<Link href="https://octane-xplat.goddardai.org"><Text>Documentation</Text></Link>
|
|
31
|
+
</HStack>
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Use `useRoute` to read the current screen and `useCanGoBack` to decide whether
|
|
36
|
+
to offer a back button. Pass the same stack name to `popRoute`.
|
|
37
|
+
|
|
38
|
+
```tsrx
|
|
39
|
+
import { Pressable, Text, useRoute, useCanGoBack, popRoute } from '@octane-xplat/ui'
|
|
40
|
+
|
|
41
|
+
export function NavigationStatus() {
|
|
42
|
+
const route = useRoute('root')
|
|
43
|
+
const canGoBack = useCanGoBack('root')
|
|
44
|
+
return <>
|
|
45
|
+
<Text>{route?.name ?? 'Home'}</Text>
|
|
46
|
+
<Pressable disabled={!canGoBack} onPress={() => popRoute('root')}>
|
|
47
|
+
<Text>Back</Text>
|
|
48
|
+
</Pressable>
|
|
49
|
+
</>
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Prefer string IDs in route params: generated route helpers use string params,
|
|
54
|
+
and IDs make URLs shareable. The low-level router JSON-encodes object params
|
|
55
|
+
on web with a warning; do not rely on it to carry application state. With a
|
|
56
|
+
registered `/projects/:id` screen, navigate using the project ID:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { pushRoute } from '@octane-xplat/ui'
|
|
60
|
+
|
|
61
|
+
pushRoute({ stack: 'root', name: 'projects/:id', params: { id: '42' } })
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Named tab stacks keep each tab's navigation separate. `Tabs` renders the tab's
|
|
65
|
+
initial content; pushing a route into that stack opens it inside the tab pane.
|
|
66
|
+
The screen below assumes `settings` is already registered.
|
|
67
|
+
|
|
68
|
+
```tsrx
|
|
69
|
+
import { Tabs, Text, Pressable, pushRoute } from '@octane-xplat/ui'
|
|
70
|
+
|
|
71
|
+
export function AppTabs() {
|
|
72
|
+
return <Tabs tabs={[
|
|
73
|
+
{ title: 'Home', stack: 'home', render: () => <Text>Home</Text> },
|
|
74
|
+
{ title: 'Account', stack: 'account', render: () =>
|
|
75
|
+
<Pressable onPress={() => pushRoute({ stack: 'account', name: 'settings', params: {} })}>
|
|
76
|
+
<Text>Settings</Text>
|
|
77
|
+
</Pressable> },
|
|
78
|
+
]} />
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For loaders, deep links, route config, and generated route types, see the
|
|
83
|
+
[navigation guide](https://octane-xplat.goddardai.org/navigation).
|
|
@@ -1,49 +1,84 @@
|
|
|
1
|
-
# Platform services
|
|
1
|
+
# Platform services and platform files
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use `@octane-xplat/platform` for common device services such as storage,
|
|
4
|
+
clipboard, permissions, connectivity, app lifecycle, URLs, and screen size.
|
|
5
|
+
Clipboard operations can fail or be unavailable; show a useful result.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
links, clipboard, `secureStorage`, haptics, notifications, `announce`,
|
|
10
|
-
`systemBars`, permissions, `openUrl`, `openSettings`. Services that need a
|
|
11
|
-
NativeScript plugin ship as leaf packages: `@octane-xplat/share`,
|
|
12
|
-
`@octane-xplat/files`, `@octane-xplat/media`, `@octane-xplat/biometrics`,
|
|
13
|
-
`@octane-xplat/geolocation`, `@octane-xplat/notifications`,
|
|
14
|
-
`@octane-xplat/secure-storage`, and `haptics` in `@octane-xplat/haptics` —
|
|
15
|
-
each leaf owns its plugin as a real dependency.
|
|
7
|
+
```tsrx
|
|
8
|
+
import { useState } from 'octane'
|
|
9
|
+
import { clipboard } from '@octane-xplat/platform'
|
|
10
|
+
import { Pressable, Text } from '@octane-xplat/ui'
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
export function CopyTrip() {
|
|
13
|
+
const [message, setMessage] = useState('Copy trip link')
|
|
14
|
+
return <Pressable onPress={async () => {
|
|
15
|
+
const copied = await clipboard.writeText('https://example.com/trips/42')
|
|
16
|
+
setMessage(copied ? 'Copied' : 'Copy unavailable')
|
|
17
|
+
}}><Text>{message}</Text></Pressable>
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Features with additional native dependencies live in separate packages:
|
|
22
|
+
`@octane-xplat/share`, `files`, `media`, `biometrics`, `geolocation`,
|
|
23
|
+
`notifications`, `secure-storage`, and `haptics`. Install the package for the
|
|
24
|
+
feature you need. Biometrics, for example, reports `unsupported` on web;
|
|
25
|
+
check permission and availability before trying to verify someone.
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
```tsrx
|
|
28
|
+
import { useState } from 'octane'
|
|
29
|
+
import { biometrics } from '@octane-xplat/biometrics'
|
|
30
|
+
import { Pressable, Text } from '@octane-xplat/ui'
|
|
31
|
+
|
|
32
|
+
export function UnlockTrip() {
|
|
33
|
+
const [message, setMessage] = useState('Unlock trip')
|
|
34
|
+
return <Pressable onPress={async () => {
|
|
35
|
+
const status = await biometrics.ensure()
|
|
36
|
+
if (status !== 'granted' || !biometrics.impl) {
|
|
37
|
+
setMessage('Use your password instead')
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
const verified = await biometrics.impl.verify('Unlock your trip')
|
|
41
|
+
setMessage(verified ? 'Unlocked' : 'Try again')
|
|
42
|
+
}}><Text>{message}</Text></Pressable>
|
|
43
|
+
}
|
|
44
|
+
```
|
|
22
45
|
|
|
23
|
-
|
|
24
|
-
|
|
46
|
+
## Write a platform file when behavior differs
|
|
47
|
+
|
|
48
|
+
Split the file when a feature needs browser or OS APIs. Import the component
|
|
49
|
+
without a suffix; the bundler selects the matching implementation.
|
|
25
50
|
|
|
26
51
|
```text
|
|
27
|
-
src/parts/Scanner.tsrx
|
|
28
|
-
src/parts/Scanner.web.tsrx browser implementation (camera via getUserMedia)
|
|
52
|
+
src/parts/Scanner.web.tsrx browser implementation
|
|
29
53
|
src/parts/Scanner.mobile.tsrx shared iOS/Android implementation
|
|
30
|
-
src/parts/Scanner.ios.tsrx iOS
|
|
54
|
+
src/parts/Scanner.ios.tsrx iOS override
|
|
55
|
+
src/parts/Scanner.tsrx native default
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```tsrx
|
|
59
|
+
import { Scanner } from './parts/Scanner'
|
|
60
|
+
|
|
61
|
+
export function ScanScreen() { return <Scanner /> }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Native JSX files start with the NativeScript pragma on line 1. Import native
|
|
65
|
+
APIs from `@nativescript/core`, and read app lifecycle state inside the call
|
|
66
|
+
rather than during module initialization. This example belongs in
|
|
67
|
+
`Status.mobile.tsrx`:
|
|
68
|
+
|
|
69
|
+
```tsrx
|
|
70
|
+
/** @jsxImportSource @nativescript-community/octane */
|
|
71
|
+
import { Application } from '@nativescript/core'
|
|
72
|
+
import { Pressable, Text } from '@octane-xplat/ui'
|
|
73
|
+
|
|
74
|
+
export function Status() {
|
|
75
|
+
return <Pressable onPress={() => console.log(Application.hasLaunched())}>
|
|
76
|
+
<Text>Check app status</Text>
|
|
77
|
+
</Pressable>
|
|
78
|
+
}
|
|
31
79
|
```
|
|
32
80
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Leaf rules:
|
|
38
|
-
|
|
39
|
-
- Inside `.web.*`, DOM APIs are expected; NativeScript imports belong in
|
|
40
|
-
`.mobile.*`, `.ios.*`, `.android.*`, or the unsuffixed native default. If
|
|
41
|
-
browser code imports that default, add a `.web.*` sibling.
|
|
42
|
-
- Never read app-level NativeScript globals (`Application.android.*`)
|
|
43
|
-
at module top — read them inside the call. Top-level reads crash cold start
|
|
44
|
-
before the app exists.
|
|
45
|
-
- Native-specific `.tsrx` files containing JSX start with
|
|
46
|
-
`/** @jsxImportSource @nativescript-community/octane */` on line 1.
|
|
47
|
-
- Deep imports under `@nativescript/core/ui/*` bundle as a second module
|
|
48
|
-
instance — import from `@nativescript/core` only.
|
|
49
|
-
- `console.debug` doesn't exist on device — use `console.log`.
|
|
81
|
+
Keep browser globals and DOM events in `.web.*` files. Do not import a second
|
|
82
|
+
renderer or deep-import NativeScript UI modules. Use `console.log` for device
|
|
83
|
+
logging. See the [platform services guide](https://octane-xplat.goddardai.org/platform-services)
|
|
84
|
+
when choosing a service or fallback.
|
|
@@ -1,42 +1,95 @@
|
|
|
1
1
|
# Styling
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use classes for static styling and inline style objects for values that change
|
|
4
|
+
while the app runs. Put the CSS in `src/style.css` and the component in a
|
|
5
|
+
`.tsrx` file.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
-
|
|
7
|
-
|
|
7
|
+
```css
|
|
8
|
+
.trip-card {
|
|
9
|
+
padding: 16px;
|
|
10
|
+
border-radius: 12px;
|
|
11
|
+
}
|
|
12
|
+
```
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
```tsrx
|
|
15
|
+
import { View, Text } from '@octane-xplat/ui'
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
export function TripCard(props: { opacity: number }) {
|
|
18
|
+
return <View className="trip-card" style={{ opacity: props.opacity }}>
|
|
19
|
+
<Text>Your next adventure</Text>
|
|
20
|
+
</View>
|
|
21
|
+
}
|
|
22
|
+
```
|
|
16
23
|
|
|
17
|
-
|
|
24
|
+
## Shared CSS
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
Shared CSS compiles for web and native. Prefer flex layouts, padding, and
|
|
27
|
+
supported transforms. The native build warns when it drops declarations such
|
|
28
|
+
as `position: fixed`, `margin: auto`, or `box-shadow`; resolve the warning
|
|
29
|
+
before depending on that style.
|
|
30
|
+
|
|
31
|
+
```css
|
|
32
|
+
.trip-card {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
padding: 16px;
|
|
36
|
+
}
|
|
37
|
+
.trip-actions {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: row;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
24
43
|
|
|
25
44
|
## Tokens and dark mode
|
|
26
45
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
The starter imports the framework's tokens. They provide shared layout rules; the starter
|
|
47
|
+
defines its app palette in `src/style.css`. Use the palette tokens in your CSS.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import '@octane-xplat/ui/theme/tokens.css'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```css
|
|
54
|
+
.trip-card {
|
|
55
|
+
background-color: var(--color-surface);
|
|
56
|
+
color: var(--color-text);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Use `useColorScheme()` to follow the system theme. Apply the matching class to
|
|
61
|
+
the root so the token colors follow the theme; a user preference can override
|
|
62
|
+
this in the starter's `src/App.tsrx`.
|
|
32
63
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
64
|
+
```tsrx
|
|
65
|
+
import { View, Text, useColorScheme } from '@octane-xplat/ui'
|
|
66
|
+
|
|
67
|
+
export function ThemePreview() {
|
|
68
|
+
const scheme = useColorScheme()
|
|
69
|
+
return <View className={scheme === 'dark' ? 'dark' : 'light'}>
|
|
70
|
+
<Text>Ready for your next trip</Text>
|
|
71
|
+
</View>
|
|
72
|
+
}
|
|
73
|
+
```
|
|
36
74
|
|
|
37
75
|
## Fonts
|
|
38
76
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
77
|
+
System fonts are the default. Add a custom font when your app needs one.
|
|
78
|
+
|
|
79
|
+
`pnpm xplat fonts add <file>` registers a font everywhere: stages a `.ttf`
|
|
80
|
+
into `src/fonts` (iOS/Android pick it up from there; `.woff`/`.woff2` inputs
|
|
81
|
+
are converted), writes the web `@font-face` into `src/fonts.css`, and wires
|
|
82
|
+
`--font-sans` (or another token via `--token mono|none|<name>`) in
|
|
83
|
+
`style.css`. Fontsource packages work too —
|
|
84
|
+
`pnpm xplat fonts add @fontsource-variable/inter --install` imports the
|
|
85
|
+
package's own CSS on web and stages its converted files natively (upright
|
|
86
|
+
faces only — italic files are skipped). Variable
|
|
87
|
+
fonts carry their weight range — `font-weight` on `Text` selects the matching
|
|
88
|
+
instance. Family names differ per target; the command reads them from the
|
|
89
|
+
file itself. See the styling guide at
|
|
42
90
|
https://octane-xplat.goddardai.org/styling for the per-target table.
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
pnpm xplat fonts add ./Inter.ttf
|
|
94
|
+
pnpm xplat fonts add @fontsource-variable/inter --install
|
|
95
|
+
```
|
package/template/package.json
CHANGED
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"@nativescript-community/octane": "0.2.4",
|
|
21
21
|
"@nativescript-community/ui-drawer": "0.1.32",
|
|
22
22
|
"@nativescript/core": "9.1.2",
|
|
23
|
-
"@octane-xplat/effects": "^0.
|
|
24
|
-
"@octane-xplat/platform": "^0.
|
|
25
|
-
"@octane-xplat/ui": "^0.
|
|
23
|
+
"@octane-xplat/effects": "^0.10.0",
|
|
24
|
+
"@octane-xplat/platform": "^0.10.0",
|
|
25
|
+
"@octane-xplat/ui": "^0.10.0",
|
|
26
26
|
"@valor/nativescript-websockets": "2.0.3",
|
|
27
27
|
"octane": "0.6.3"
|
|
28
28
|
},
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"@nativescript/ios": "9.1.0",
|
|
33
33
|
"@nativescript/types": "9.0.0",
|
|
34
34
|
"@nativescript/vite": "8.0.17",
|
|
35
|
-
"@octane-xplat/cli": "^0.
|
|
36
|
-
"@octane-xplat/lint": "^0.
|
|
35
|
+
"@octane-xplat/cli": "^0.10.0",
|
|
36
|
+
"@octane-xplat/lint": "^0.10.0",
|
|
37
37
|
"@octanejs/vite-plugin": "0.1.61",
|
|
38
38
|
"@tsrx/typescript-plugin": "0.4.11",
|
|
39
39
|
"nativescript": "9.1.1",
|