react-native-terra-ui 0.3.0 → 0.4.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/CHANGELOG.md +13 -0
- package/lib/module/components/screen/ScreenFlashList.js +32 -26
- package/lib/module/components/screen/ScreenFlashList.js.map +1 -1
- package/lib/module/components/screen/ScreenFlatList.js +37 -31
- package/lib/module/components/screen/ScreenFlatList.js.map +1 -1
- package/lib/module/components/screen/ScreenScrollView.js +24 -18
- package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
- package/lib/module/context/ThemeProvider.js +15 -0
- package/lib/module/context/ThemeProvider.js.map +1 -1
- package/lib/module/context/index.js +1 -1
- package/lib/module/context/index.js.map +1 -1
- package/lib/module/theme/breakpoints.js +29 -0
- package/lib/module/theme/breakpoints.js.map +1 -0
- package/lib/module/theme/index.js +4 -0
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/registry.js +3 -1
- package/lib/module/theme/registry.js.map +1 -1
- package/lib/module/theme/screen-margin.js +17 -0
- package/lib/module/theme/screen-margin.js.map +1 -0
- package/lib/module/theme/tokens/primitives.js +12 -2
- package/lib/module/theme/tokens/primitives.js.map +1 -1
- package/lib/typescript/src/components/screen/ScreenFlashList.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenScrollView.d.ts.map +1 -1
- package/lib/typescript/src/context/ThemeProvider.d.ts +8 -0
- package/lib/typescript/src/context/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/src/context/index.d.ts +1 -1
- package/lib/typescript/src/context/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/breakpoints.d.ts +32 -0
- package/lib/typescript/src/theme/breakpoints.d.ts.map +1 -0
- package/lib/typescript/src/theme/index.d.ts +4 -0
- package/lib/typescript/src/theme/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/registry.d.ts.map +1 -1
- package/lib/typescript/src/theme/screen-margin.d.ts +15 -0
- package/lib/typescript/src/theme/screen-margin.d.ts.map +1 -0
- package/lib/typescript/src/theme/tokens/primitives.d.ts +10 -2
- package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
- package/lib/typescript/src/theme/types.d.ts +9 -3
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/package.json +1 -2
- package/src/components/screen/ScreenFlashList.tsx +37 -30
- package/src/components/screen/ScreenFlatList.tsx +41 -34
- package/src/components/screen/ScreenScrollView.tsx +24 -18
- package/src/context/ThemeProvider.tsx +15 -0
- package/src/context/index.ts +1 -1
- package/src/theme/breakpoints.ts +41 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/registry.ts +2 -1
- package/src/theme/screen-margin.ts +26 -0
- package/src/theme/tokens/primitives.ts +12 -2
- package/src/theme/types.ts +12 -2
- package/skills/terra-ui/SKILL.md +0 -193
- package/skills/terra-ui/references/recipes.md +0 -198
- package/skills/terra-ui/references/troubleshooting.md +0 -112
|
@@ -450,8 +450,18 @@ export const primitives = {
|
|
|
450
450
|
'opacity.pressed': 0.85,
|
|
451
451
|
|
|
452
452
|
// ── screen-level layout ───────────────────────────────────────────────────
|
|
453
|
-
|
|
454
|
-
|
|
453
|
+
// `margin.x`/`margin.y` both scale up with the viewport breakpoint (see
|
|
454
|
+
// `theme/breakpoints.ts`).
|
|
455
|
+
'layout.screen.margin.x.xs': 16,
|
|
456
|
+
'layout.screen.margin.x.sm': 16,
|
|
457
|
+
'layout.screen.margin.x.md': 24,
|
|
458
|
+
'layout.screen.margin.x.lg': 32,
|
|
459
|
+
'layout.screen.margin.x.xl': 48,
|
|
460
|
+
'layout.screen.margin.y.xs': 16,
|
|
461
|
+
'layout.screen.margin.y.sm': 16,
|
|
462
|
+
'layout.screen.margin.y.md': 24,
|
|
463
|
+
'layout.screen.margin.y.lg': 32,
|
|
464
|
+
'layout.screen.margin.y.xl': 48,
|
|
455
465
|
|
|
456
466
|
// ── header layout ─────────────────────────────────────────────────────────
|
|
457
467
|
// Header bar height (compact nav bar / collapsed large-title bar). 56dp aligns
|
package/src/theme/types.ts
CHANGED
|
@@ -9,6 +9,8 @@ import type { ImageStyle, StyleProp } from 'react-native';
|
|
|
9
9
|
|
|
10
10
|
import type { DeepPartial } from '#utils/deep-merge';
|
|
11
11
|
|
|
12
|
+
import type { BreakpointName } from './breakpoints';
|
|
13
|
+
|
|
12
14
|
// ─── Scale keys (declared explicitly; token data is untyped) ────────────────
|
|
13
15
|
|
|
14
16
|
/** Spacing scale keys (dp, 4dp base unit). Read as `gap={3}` → string '3'. */
|
|
@@ -221,8 +223,16 @@ export interface OpacityTokens {
|
|
|
221
223
|
/** Screen-level layout tokens. */
|
|
222
224
|
export interface LayoutTokens {
|
|
223
225
|
screen: {
|
|
224
|
-
/**
|
|
225
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Margin between content and the screen edge (applied as container
|
|
228
|
+
* padding). Both axes scale with the viewport breakpoint — resolve with
|
|
229
|
+
* `resolveBreakpointValue(theme.layout.screen.margin.x, rt.breakpoint)`
|
|
230
|
+
* (same for `y`).
|
|
231
|
+
*/
|
|
232
|
+
margin: {
|
|
233
|
+
x: Record<BreakpointName, number>;
|
|
234
|
+
y: Record<BreakpointName, number>;
|
|
235
|
+
};
|
|
226
236
|
};
|
|
227
237
|
header: {
|
|
228
238
|
/** Height of the header bar (compact nav bar / collapsed large-title bar), dp. */
|
package/skills/terra-ui/SKILL.md
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: terra-ui
|
|
3
|
-
description: Build React Native UI with react-native-terra-ui — setup, component selection, theming, and the traps. Use when writing screens or components in an app that depends on react-native-terra-ui.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Terra UI
|
|
7
|
-
|
|
8
|
-
`react-native-terra-ui` is a themed React Native component library built on Unistyles v3.
|
|
9
|
-
|
|
10
|
-
**This skill ships inside the package**, so it describes the version you have installed.
|
|
11
|
-
Check it with `node -p "require('react-native-terra-ui/package.json').version"`.
|
|
12
|
-
|
|
13
|
-
**Do not guess prop names.** The package publishes both `src` and TypeScript
|
|
14
|
-
declarations, so the real props are on disk — read
|
|
15
|
-
`node_modules/react-native-terra-ui/lib/typescript/src/components/<name>/` or the `src`
|
|
16
|
-
next to it. Every prop carries a JSDoc line with its default. This skill deliberately does
|
|
17
|
-
not restate props, because a copy would drift from the types.
|
|
18
|
-
|
|
19
|
-
## Before You Write A Screen
|
|
20
|
-
|
|
21
|
-
Four things break most often, in this order:
|
|
22
|
-
|
|
23
|
-
1. **Expo Go does not work.** Unistyles v3 needs native modules — use a development build
|
|
24
|
-
(`npx expo run:ios`, or an EAS dev client). If components render unstyled or the app
|
|
25
|
-
crashes at startup, check this first.
|
|
26
|
-
2. **The Babel plugin must list the library.** `react-native-unistyles/plugin` needs
|
|
27
|
-
`autoProcessImports: ['react-native-terra-ui']` so the library's own stylesheets are
|
|
28
|
-
processed, and `react-native-worklets/plugin` must be **last**. Without it, styles
|
|
29
|
-
freeze at whichever theme was active when they were created.
|
|
30
|
-
3. **`TerraUIProvider` wraps the app once**, near the root. It supplies `SafeAreaProvider`
|
|
31
|
-
and accent state. It does **not** include `ToastProvider` — add that separately if you
|
|
32
|
-
use toasts.
|
|
33
|
-
4. **Import order decides whether your theme config wins.** See below.
|
|
34
|
-
|
|
35
|
-
## Import Order Is Load-Bearing
|
|
36
|
-
|
|
37
|
-
| Entry | Side effect |
|
|
38
|
-
|---|---|
|
|
39
|
-
| `react-native-terra-ui/theme` | none — safe anywhere |
|
|
40
|
-
| `react-native-terra-ui` | auto-calls `configureTerraUI()` with defaults **if not already configured** |
|
|
41
|
-
|
|
42
|
-
So a custom theme must be configured *before* the first root-entry import. The pattern:
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
// terra-ui.ts — imports from /theme only, so it has no side effects of its own
|
|
46
|
-
import { configureTerraUI, type TerraTheme } from 'react-native-terra-ui/theme';
|
|
47
|
-
|
|
48
|
-
declare module 'react-native-unistyles' {
|
|
49
|
-
interface UnistylesThemes { light: TerraTheme; dark: TerraTheme }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
configureTerraUI({ /* shared, accents, defaultAccent, icons, image, components */ });
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
```js
|
|
56
|
-
// index.js — the bootstrap goes FIRST
|
|
57
|
-
import './terra-ui';
|
|
58
|
-
import 'expo-router/entry';
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
Get this wrong and there is no error — the defaults silently win. If your fonts, accent, or
|
|
62
|
-
component defaults appear to be ignored, this is why.
|
|
63
|
-
|
|
64
|
-
Skip all of it if the default theme is fine; just wrap in `TerraUIProvider`.
|
|
65
|
-
|
|
66
|
-
## Styling Rules
|
|
67
|
-
|
|
68
|
-
Styles must go through Unistyles, not React Native's `StyleSheet`:
|
|
69
|
-
|
|
70
|
-
```tsx
|
|
71
|
-
import { StyleSheet } from 'react-native-unistyles'; // ✅ not from 'react-native'
|
|
72
|
-
|
|
73
|
-
const styles = StyleSheet.create((theme, rt) => ({
|
|
74
|
-
card: {
|
|
75
|
-
backgroundColor: theme.color['surface.default'], // flat, dotted keys
|
|
76
|
-
padding: theme.spacing['4'], // string keys, 4dp base
|
|
77
|
-
borderRadius: theme.radius.md,
|
|
78
|
-
paddingTop: rt.insets.top,
|
|
79
|
-
},
|
|
80
|
-
}));
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
- **Colors are flat dotted string keys** — `theme.color['surface.default']`,
|
|
84
|
-
`theme.color['status.danger.fg']`. The one exception is `theme.color.background`.
|
|
85
|
-
- Semantic tokens only. There is no palette export — that is deliberate.
|
|
86
|
-
- Put theme and variant decisions **inside** `StyleSheet.create`. Do not destructure
|
|
87
|
-
`theme` from `useUnistyles()` to build a style object; it defeats the native styling
|
|
88
|
-
path and stops updating on theme change.
|
|
89
|
-
- Use `variants` / `compoundVariants` rather than conditional style arrays.
|
|
90
|
-
- Runtime values (`insets`, screen size) come from the second `(theme, rt)` argument, never
|
|
91
|
-
`Dimensions.get()`. No breakpoints are configured by default.
|
|
92
|
-
- Outside a stylesheet, turn a token into a color with
|
|
93
|
-
`resolveThemeColor(token, theme)` from `react-native-terra-ui/theme`. It passes raw
|
|
94
|
-
literals (`#7c3aed`, `rgba(…)`, `transparent`) straight through.
|
|
95
|
-
|
|
96
|
-
## Choosing A Component
|
|
97
|
-
|
|
98
|
-
| Need | Use | Not |
|
|
99
|
-
|---|---|---|
|
|
100
|
-
| A screen container with margins + background | `Screen` | a bare `View` |
|
|
101
|
-
| A scrolling screen body | `Screen.ScrollView` / `.FlatList` / `.FlashList` | a plain `ScrollView` — these reserve the header inset |
|
|
102
|
-
| A nav bar | `Screen.Header as={Header.Title}` (or `as={Header.LargeTitle}`) | a custom absolute view |
|
|
103
|
-
| Icon actions in a header | `Toolbar.Button`, grouped in `Toolbar.Group` | `Button` with `isIconOnly` |
|
|
104
|
-
| A labelled action | `Button` | `Toolbar.Button` |
|
|
105
|
-
| A tag / status pill | `Chip` | `Button variant="ghost"` |
|
|
106
|
-
| A plain themed box with token props | `Box` | `Surface`, unless you want its configured radius + elevation |
|
|
107
|
-
| An elevated card | `Surface` | `Box` + hand-rolled shadow |
|
|
108
|
-
| Text | `Text` with a `variant` | RN `Text` |
|
|
109
|
-
|
|
110
|
-
`Box` is the only family exposing token style props (`p`, `px`, `gap`, `bg`, `radius`,
|
|
111
|
-
`row`, `align`, `justify`, `flex`, `elevation`, …). `Surface` extends it minus `bg`.
|
|
112
|
-
Everything else takes explicit props.
|
|
113
|
-
|
|
114
|
-
`Screen.Header` is a **polymorphic marker slot**, not a wrapper. Render the header through
|
|
115
|
-
`as`, and any extra props forward to it:
|
|
116
|
-
|
|
117
|
-
```tsx
|
|
118
|
-
<Screen.Header as={Header.LargeTitle} title="Library" subtitle="24 items" />
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Nesting the header as a child still works and appears throughout older code, but `as` is
|
|
122
|
-
the current form. Either way the slot must be a **direct** child of `Screen` — that is how
|
|
123
|
-
`Screen` detects a header and drops its top safe-area edge.
|
|
124
|
-
|
|
125
|
-
`Header.LargeTitle` collapses on scroll, so it also has to be paired with one of the
|
|
126
|
-
`Screen` scroll containers — it reads scroll position from their shared context.
|
|
127
|
-
|
|
128
|
-
## Icons Are Registered By You
|
|
129
|
-
|
|
130
|
-
The library ships only eight semantic icons: `navigation.back`, `navigation.forward`,
|
|
131
|
-
`navigation.close`, `status.info`, `status.success`, `status.warning`, `status.danger`,
|
|
132
|
-
`person`.
|
|
133
|
-
|
|
134
|
-
Everything else is yours, registered by name through declaration merging plus the `icons`
|
|
135
|
-
map. Both halves are required — types alone will not render, and a name that is not
|
|
136
|
-
registered logs a dev warning and renders **nothing**:
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
// terra-ui-icons.d.ts
|
|
140
|
-
import type { TerraIconComponent } from 'react-native-terra-ui/theme';
|
|
141
|
-
|
|
142
|
-
declare global {
|
|
143
|
-
namespace TerraUI {
|
|
144
|
-
interface IconRegistry { add: TerraIconComponent; share: TerraIconComponent }
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
declare module 'react-native-terra-ui/theme' {
|
|
148
|
-
interface TerraIconRegistry extends TerraUI.IconRegistry {}
|
|
149
|
-
}
|
|
150
|
-
declare module 'react-native-terra-ui' {
|
|
151
|
-
interface TerraIconRegistry extends TerraUI.IconRegistry {}
|
|
152
|
-
}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
```ts
|
|
156
|
-
// in configureTerraUI
|
|
157
|
-
import { Plus, Share } from 'lucide-react-native';
|
|
158
|
-
configureTerraUI({ icons: { add: Plus, share: Share } });
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
If an icon is invisible, check that it is in **both** places.
|
|
162
|
-
|
|
163
|
-
## Theme Switching
|
|
164
|
-
|
|
165
|
-
Runtime switching goes through the library, not React state:
|
|
166
|
-
|
|
167
|
-
```tsx
|
|
168
|
-
const { theme, scheme, setScheme, accent, setAccent } = useTheme(); // under TerraUIProvider
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
`setScheme` disables system-adaptive theming. `setAccent` takes a name registered in
|
|
172
|
-
`configureTerraUI({ accents })`. Re-rendering a provider with a different theme object is
|
|
173
|
-
not how this works — Unistyles resolves styles natively.
|
|
174
|
-
|
|
175
|
-
## Compound Components
|
|
176
|
-
|
|
177
|
-
Sub-components hang off the parent: `Screen.Header` / `.ScrollView` / `.FlatList` /
|
|
178
|
-
`.FlashList`, `Header.Title` / `.LargeTitle`, `Button.Icon` / `.Label`,
|
|
179
|
-
`Chip.Icon`, `Toast.Title` / `.Description` / `.Action` / `.Close` / `.Icon`,
|
|
180
|
-
`Toolbar.Group` / `.Button`, `Portal.Provider` / `.Host`.
|
|
181
|
-
|
|
182
|
-
They inherit color and size from the parent through context, so
|
|
183
|
-
`<Chip color="success"><Chip.Icon name="status.success" />Done</Chip>` needs no color on
|
|
184
|
-
the icon. An explicit prop on the child always wins.
|
|
185
|
-
|
|
186
|
-
Content goes through `children`, not `label`/`icon`/`items` props. Text children are
|
|
187
|
-
auto-wrapped in themed `Text` where the component supports a label.
|
|
188
|
-
|
|
189
|
-
## References
|
|
190
|
-
|
|
191
|
-
- `references/recipes.md` — working screen, header, toolbar, list, and toast snippets.
|
|
192
|
-
- `references/troubleshooting.md` — symptom → cause for the failures above.
|
|
193
|
-
- Full guide: https://github.com/earthling-std/react-native-terra-ui#readme
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
# Recipes
|
|
2
|
-
|
|
3
|
-
Read the component's types before adapting these — the props are on disk under
|
|
4
|
-
`node_modules/react-native-terra-ui/lib/typescript/src/components/`.
|
|
5
|
-
|
|
6
|
-
## App Root
|
|
7
|
-
|
|
8
|
-
```tsx
|
|
9
|
-
// index.js
|
|
10
|
-
import './terra-ui'; // configureTerraUI, FIRST
|
|
11
|
-
import 'expo-router/entry';
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
// app/_layout.tsx
|
|
16
|
-
import { TerraUIProvider, ToastProvider } from 'react-native-terra-ui';
|
|
17
|
-
|
|
18
|
-
export default function RootLayout() {
|
|
19
|
-
return (
|
|
20
|
-
<TerraUIProvider>
|
|
21
|
-
<ToastProvider>
|
|
22
|
-
<Stack screenOptions={{ headerShown: false }} />
|
|
23
|
-
</ToastProvider>
|
|
24
|
-
</TerraUIProvider>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
`TerraUIProvider` already renders `SafeAreaProvider`. Add your own only if something above
|
|
30
|
-
it needs insets. `ToastProvider` is separate and only needed if you use toasts.
|
|
31
|
-
|
|
32
|
-
## A Screen With A Collapsing Header
|
|
33
|
-
|
|
34
|
-
The large title collapses as the scroll container moves, so the two must be paired.
|
|
35
|
-
|
|
36
|
-
```tsx
|
|
37
|
-
import { Header, Screen, Text } from 'react-native-terra-ui';
|
|
38
|
-
|
|
39
|
-
export function LibraryScreen() {
|
|
40
|
-
return (
|
|
41
|
-
<Screen>
|
|
42
|
-
<Screen.Header as={Header.LargeTitle} title="Library" subtitle="24 items" />
|
|
43
|
-
|
|
44
|
-
<Screen.ScrollView>
|
|
45
|
-
<Text variant="body-md">Content starts below the bar automatically.</Text>
|
|
46
|
-
</Screen.ScrollView>
|
|
47
|
-
</Screen>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
`Screen.Header` is polymorphic: pass the header through `as` and its props flow straight
|
|
53
|
-
on. Older code nests the header as a child instead
|
|
54
|
-
(`<Screen.Header><Header.LargeTitle …/></Screen.Header>`); that still renders, but `as` is
|
|
55
|
-
the current form — prefer it.
|
|
56
|
-
|
|
57
|
-
The scroll containers reserve the header height plus the top safe-area inset as content
|
|
58
|
-
padding — do not add your own top padding for the header.
|
|
59
|
-
|
|
60
|
-
`margins` controls the screen's horizontal/vertical content margins
|
|
61
|
-
(`'all' | 'x' | 'y' | 'none'`, default `'all'`); set it on `Screen` or override per
|
|
62
|
-
container. For a full-bleed carousel use `margins="none"`.
|
|
63
|
-
|
|
64
|
-
## A Compact Header With Actions
|
|
65
|
-
|
|
66
|
-
```tsx
|
|
67
|
-
import { Header, Screen, Toolbar } from 'react-native-terra-ui';
|
|
68
|
-
|
|
69
|
-
<Screen.Header
|
|
70
|
-
as={Header.Title}
|
|
71
|
-
title="Album"
|
|
72
|
-
dismissAction="back"
|
|
73
|
-
onDismiss={() => router.back()}
|
|
74
|
-
RightToolbar={
|
|
75
|
-
<>
|
|
76
|
-
<Toolbar.Group>
|
|
77
|
-
<Toolbar.Button icon="add" accessibilityLabel="Add" onPress={add} />
|
|
78
|
-
<Toolbar.Button icon="download" accessibilityLabel="Save" onPress={save} />
|
|
79
|
-
</Toolbar.Group>
|
|
80
|
-
<Toolbar.Button icon="share" accessibilityLabel="Share" onPress={share} />
|
|
81
|
-
</>
|
|
82
|
-
}
|
|
83
|
-
/>
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
The header wraps the slot in a `Toolbar` row for you — pass `Toolbar.Group` /
|
|
87
|
-
`Toolbar.Button` children directly, not your own `Toolbar`.
|
|
88
|
-
|
|
89
|
-
`dismissAction` is `'back' | 'close' | 'none'`. Pass `navigation` (or `onDismiss`) so it
|
|
90
|
-
knows how to leave.
|
|
91
|
-
|
|
92
|
-
## A Toolbar Outside A Header
|
|
93
|
-
|
|
94
|
-
Anywhere else, supply the row yourself:
|
|
95
|
-
|
|
96
|
-
```tsx
|
|
97
|
-
<Toolbar style={{ position: 'absolute', right: 16, bottom: 24 }}>
|
|
98
|
-
<Toolbar.Group background="surface.raised">
|
|
99
|
-
<Toolbar.Button icon="add" accessibilityLabel="Add" onPress={add} />
|
|
100
|
-
</Toolbar.Group>
|
|
101
|
-
<Toolbar.Button icon="share" accessibilityLabel="Share" onPress={share} background="surface.raised" />
|
|
102
|
-
</Toolbar>
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
One `Toolbar.Group` is one capsule. Items inside share it; a `Toolbar.Button` that is a
|
|
106
|
-
direct child of the row keeps its own circle. Text labels work too —
|
|
107
|
-
`<Toolbar.Button onPress={save}>Save</Toolbar.Button>` — and the cell grows to fit.
|
|
108
|
-
|
|
109
|
-
## A List
|
|
110
|
-
|
|
111
|
-
```tsx
|
|
112
|
-
<Screen>
|
|
113
|
-
<Screen.Header as={Header.LargeTitle} title="Inbox" />
|
|
114
|
-
|
|
115
|
-
<Screen.FlatList
|
|
116
|
-
data={items}
|
|
117
|
-
keyExtractor={(item) => item.id}
|
|
118
|
-
renderItem={({ item }) => <Row item={item} />}
|
|
119
|
-
loading={isLoading}
|
|
120
|
-
EmptyComponent={<EmptyState title="Nothing here yet" />}
|
|
121
|
-
/>
|
|
122
|
-
</Screen>
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
All three scroll containers share `loading`, `LoadingComponent`, `EmptyComponent`, and
|
|
126
|
-
`margins`. `loading` wins over `EmptyComponent` while data is in flight, so a first load
|
|
127
|
-
shows a spinner rather than an empty state. Use `Screen.FlashList` for long lists.
|
|
128
|
-
|
|
129
|
-
## Toasts
|
|
130
|
-
|
|
131
|
-
```tsx
|
|
132
|
-
import { Toast } from 'react-native-terra-ui';
|
|
133
|
-
|
|
134
|
-
Toast.show({ label: 'Saved', variant: 'success' });
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Imperative statics — no hook needed at the call site, but `ToastProvider` must be mounted.
|
|
138
|
-
`useToast()` is there if you need the queue. The headline field is `label`, not `title`;
|
|
139
|
-
`ToastShowConfig` also takes `description`, `icon`, `actionLabel`, `onActionPress`,
|
|
140
|
-
`duration` (a number or `'persistent'`), `placement`, and `showCloseButton`.
|
|
141
|
-
|
|
142
|
-
## Themed Styles
|
|
143
|
-
|
|
144
|
-
```tsx
|
|
145
|
-
import { StyleSheet } from 'react-native-unistyles';
|
|
146
|
-
|
|
147
|
-
const styles = StyleSheet.create((theme, rt) => ({
|
|
148
|
-
card: {
|
|
149
|
-
backgroundColor: theme.color['surface.raised'],
|
|
150
|
-
borderRadius: theme.radius.lg,
|
|
151
|
-
padding: theme.spacing['4'],
|
|
152
|
-
gap: theme.spacing['2'],
|
|
153
|
-
marginBottom: rt.insets.bottom,
|
|
154
|
-
variants: {
|
|
155
|
-
emphasis: {
|
|
156
|
-
default: {},
|
|
157
|
-
strong: { borderWidth: 1, borderColor: theme.color['border.strong'] },
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
}));
|
|
162
|
-
|
|
163
|
-
// in the component
|
|
164
|
-
styles.useVariants({ emphasis });
|
|
165
|
-
<View style={styles.card} />
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## Token Props Without A Stylesheet
|
|
169
|
-
|
|
170
|
-
For simple layout, `Box` avoids a stylesheet entirely:
|
|
171
|
-
|
|
172
|
-
```tsx
|
|
173
|
-
<Box row align="center" justify="between" gap="3" p="4" bg="surface.default" radius="lg">
|
|
174
|
-
<Text variant="label-md">Total</Text>
|
|
175
|
-
<Text variant="title-sm">$42</Text>
|
|
176
|
-
</Box>
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
`asChild` merges Box's props onto a single child instead of rendering a `View` — useful for
|
|
180
|
-
router links:
|
|
181
|
-
|
|
182
|
-
```tsx
|
|
183
|
-
<Link href="/settings" asChild>
|
|
184
|
-
<Button variant="outline">Settings</Button>
|
|
185
|
-
</Link>
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
## Theme Toggle
|
|
189
|
-
|
|
190
|
-
```tsx
|
|
191
|
-
const { scheme, setScheme, accent, setAccent } = useTheme();
|
|
192
|
-
|
|
193
|
-
<Button onPress={() => setScheme(scheme === 'dark' ? 'light' : 'dark')}>
|
|
194
|
-
{scheme === 'dark' ? 'Light' : 'Dark'}
|
|
195
|
-
</Button>
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
`setAccent('brand')` needs `brand` registered in `configureTerraUI({ accents })`.
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
# Troubleshooting
|
|
2
|
-
|
|
3
|
-
Symptom → cause. Most Terra UI problems are setup problems, and most of them fail
|
|
4
|
-
**silently** rather than throwing.
|
|
5
|
-
|
|
6
|
-
## Components render unstyled, or the app crashes on launch
|
|
7
|
-
|
|
8
|
-
Expo Go. Unistyles v3 requires native modules, so Expo Go cannot run it. Build a
|
|
9
|
-
development client:
|
|
10
|
-
|
|
11
|
-
```sh
|
|
12
|
-
npx expo run:ios # or run:android
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Styles do not change when the theme changes
|
|
16
|
-
|
|
17
|
-
The Unistyles Babel plugin is not processing the library. Add it to `autoProcessImports`,
|
|
18
|
-
and keep the worklets plugin last:
|
|
19
|
-
|
|
20
|
-
```js
|
|
21
|
-
plugins: [
|
|
22
|
-
['react-native-unistyles/plugin', {
|
|
23
|
-
root: path.resolve(__dirname, 'src'),
|
|
24
|
-
autoProcessImports: ['react-native-terra-ui'],
|
|
25
|
-
}],
|
|
26
|
-
'react-native-worklets/plugin', // MUST be last
|
|
27
|
-
]
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Clear the cache after changing Babel config — `npx expo start --clear`.
|
|
31
|
-
|
|
32
|
-
The same symptom appears in **your own** components if you built the style object in the
|
|
33
|
-
component body from `useUnistyles()`. Move it into `StyleSheet.create`.
|
|
34
|
-
|
|
35
|
-
## My theme config is ignored
|
|
36
|
-
|
|
37
|
-
Import order. Something imported `react-native-terra-ui` (the root entry) before your
|
|
38
|
-
`configureTerraUI()` call ran, so the auto-default won and later calls are no-ops.
|
|
39
|
-
|
|
40
|
-
- The bootstrap file must import from `react-native-terra-ui/theme` only.
|
|
41
|
-
- It must be the **first** import in your app entry, before `expo-router/entry` or your
|
|
42
|
-
`AppRegistry` registration.
|
|
43
|
-
|
|
44
|
-
There is no warning for this. Verify with `getIsConfigured()` from
|
|
45
|
-
`react-native-terra-ui/theme` before your call.
|
|
46
|
-
|
|
47
|
-
## An icon renders nothing
|
|
48
|
-
|
|
49
|
-
The name is not registered. Only eight semantic names ship with the library
|
|
50
|
-
(`navigation.back/forward/close`, `status.info/success/warning/danger`, `person`); every
|
|
51
|
-
other name must be in **both** the `TerraUI.IconRegistry` declaration and the `icons` map
|
|
52
|
-
passed to `configureTerraUI`. A missing name logs a dev warning and renders `null` — the
|
|
53
|
-
layout still reserves space, so it looks like a blank gap.
|
|
54
|
-
|
|
55
|
-
## Content sits under the header, or too far below it
|
|
56
|
-
|
|
57
|
-
Use a `Screen` scroll container (`Screen.ScrollView` / `.FlatList` / `.FlashList`) rather
|
|
58
|
-
than a plain `ScrollView`. They reserve the header height plus the top inset as content
|
|
59
|
-
padding. Do not add your own top padding on top of that.
|
|
60
|
-
|
|
61
|
-
If content lands too *low* on first paint in a native tab screen, `react-native-screens`
|
|
62
|
-
is also applying UIKit's automatic content inset. Opt out at the navigator — on an
|
|
63
|
-
expo-router native tab that is `disableAutomaticContentInsets` on the trigger.
|
|
64
|
-
|
|
65
|
-
## `Header.LargeTitle` does not collapse
|
|
66
|
-
|
|
67
|
-
It reads scroll position from the `Screen` context, so it needs both halves:
|
|
68
|
-
|
|
69
|
-
- rendered through `Screen.Header` — `<Screen.Header as={Header.LargeTitle} title="…" />`
|
|
70
|
-
(or nested inside it, the older form), as a direct child of `Screen`, and
|
|
71
|
-
- paired with `Screen.ScrollView` / `.FlatList` / `.FlashList` in the same `Screen`.
|
|
72
|
-
|
|
73
|
-
A plain `ScrollView` sibling will not drive it.
|
|
74
|
-
|
|
75
|
-
## `useScreen()` / toast hooks throw
|
|
76
|
-
|
|
77
|
-
They require their provider. `useScreen()` must be under a `Screen`; `useToast()` and
|
|
78
|
-
`Toast.show()` need `ToastProvider` mounted (it is **not** included in `TerraUIProvider`).
|
|
79
|
-
|
|
80
|
-
## A token prop does not work
|
|
81
|
-
|
|
82
|
-
Token style props (`p`, `gap`, `bg`, `radius`, `row`, …) exist on `Box` and `Surface`
|
|
83
|
-
only. Every other component takes explicit props — read its types.
|
|
84
|
-
|
|
85
|
-
## A color token has no effect
|
|
86
|
-
|
|
87
|
-
Colors are flat dotted keys, not nested objects:
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
theme.color['surface.default'] // ✅
|
|
91
|
-
theme.color.surface.default // ✗ undefined
|
|
92
|
-
theme.color.background // ✅ the one single-segment token
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
An unknown key yields `undefined`, which React Native ignores silently.
|
|
96
|
-
|
|
97
|
-
## My `Surface` has a radius or shadow I did not ask for
|
|
98
|
-
|
|
99
|
-
`Surface` reads `radius` and `elevation` from `configureTerraUI({ components: { surface } })`.
|
|
100
|
-
That is the point of it — but if you want a neutral container, use `Box`.
|
|
101
|
-
|
|
102
|
-
## Types are missing or stale after upgrading
|
|
103
|
-
|
|
104
|
-
The package ships declarations at `lib/typescript`. Reinstall, and restart the TS server in
|
|
105
|
-
your editor. If you use the `"source"` export condition (monorepo/Metro), you are compiling
|
|
106
|
-
the library's TypeScript directly, so its own type errors surface in your build.
|
|
107
|
-
|
|
108
|
-
## Something looks wrong and none of the above applies
|
|
109
|
-
|
|
110
|
-
Read the component's source — it ships in the package under
|
|
111
|
-
`node_modules/react-native-terra-ui/src/components/<name>/`. The JSDoc on each prop states
|
|
112
|
-
its default, and it is the version you actually have installed.
|