@ranimontagna/agent-toolkit 0.1.5 → 0.1.6
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 +42 -8
- package/package.json +1 -1
- package/skills/frontend/react/react-patterns/LICENSE +21 -0
- package/skills/frontend/react/react-patterns/NOTICE.md +11 -0
- package/skills/frontend/react/react-patterns/SKILL.md +341 -0
- package/skills/frontend/react/react-performance/LICENSE +21 -0
- package/skills/frontend/react/react-performance/NOTICE.md +11 -0
- package/skills/frontend/react/react-performance/SKILL.md +574 -0
- package/skills/frontend/react/react-testing/LICENSE +21 -0
- package/skills/frontend/react/react-testing/NOTICE.md +11 -0
- package/skills/frontend/react/react-testing/SKILL.md +423 -0
- package/skills/frontend/react-native/react-native-expert/LICENSE +21 -0
- package/skills/frontend/react-native/react-native-expert/NOTICE.md +11 -0
- package/skills/frontend/react-native/react-native-expert/SKILL.md +187 -0
- package/skills/frontend/react-native/react-native-expert/references/expo-router.md +187 -0
- package/skills/frontend/react-native/react-native-expert/references/list-optimization.md +204 -0
- package/skills/frontend/react-native/react-native-expert/references/platform-handling.md +188 -0
- package/skills/frontend/react-native/react-native-expert/references/project-structure.md +171 -0
- package/skills/frontend/react-native/react-native-expert/references/storage-hooks.md +173 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/LICENSE +21 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/NOTICE.md +11 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/SKILL.md +159 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/references/api-reference.md +495 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/references/common-issues.md +389 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/references/setup-guide.md +217 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/references/styling-patterns.md +705 -0
- package/skills/frontend/react-native/react-native-unistyles-v3/references/third-party-integration.md +318 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Project Structure
|
|
2
|
+
|
|
3
|
+
## Expo Router Structure
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
my-app/
|
|
7
|
+
├── app/ # File-based routing
|
|
8
|
+
│ ├── _layout.tsx # Root layout
|
|
9
|
+
│ ├── index.tsx # Home screen
|
|
10
|
+
│ ├── +not-found.tsx # 404 screen
|
|
11
|
+
│ ├── (tabs)/ # Tab navigator group
|
|
12
|
+
│ │ ├── _layout.tsx
|
|
13
|
+
│ │ ├── index.tsx
|
|
14
|
+
│ │ ├── search.tsx
|
|
15
|
+
│ │ └── profile.tsx
|
|
16
|
+
│ ├── (auth)/ # Auth screens (no tabs)
|
|
17
|
+
│ │ ├── _layout.tsx
|
|
18
|
+
│ │ ├── login.tsx
|
|
19
|
+
│ │ └── register.tsx
|
|
20
|
+
│ └── [id].tsx # Dynamic route
|
|
21
|
+
├── components/
|
|
22
|
+
│ ├── ui/ # Reusable UI components
|
|
23
|
+
│ │ ├── Button.tsx
|
|
24
|
+
│ │ ├── Card.tsx
|
|
25
|
+
│ │ └── Input.tsx
|
|
26
|
+
│ └── features/ # Feature-specific components
|
|
27
|
+
│ ├── ProductCard.tsx
|
|
28
|
+
│ └── UserAvatar.tsx
|
|
29
|
+
├── hooks/
|
|
30
|
+
│ ├── useAuth.ts
|
|
31
|
+
│ ├── useStorage.ts
|
|
32
|
+
│ └── useApi.ts
|
|
33
|
+
├── services/
|
|
34
|
+
│ ├── api.ts # API client
|
|
35
|
+
│ └── auth.ts # Auth service
|
|
36
|
+
├── stores/
|
|
37
|
+
│ └── useUserStore.ts # Zustand stores
|
|
38
|
+
├── constants/
|
|
39
|
+
│ ├── colors.ts
|
|
40
|
+
│ └── layout.ts
|
|
41
|
+
├── types/
|
|
42
|
+
│ └── index.ts
|
|
43
|
+
├── utils/
|
|
44
|
+
│ └── helpers.ts
|
|
45
|
+
├── assets/
|
|
46
|
+
│ ├── images/
|
|
47
|
+
│ └── fonts/
|
|
48
|
+
├── app.json
|
|
49
|
+
├── babel.config.js
|
|
50
|
+
└── tsconfig.json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## app.json Configuration
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"expo": {
|
|
58
|
+
"name": "My App",
|
|
59
|
+
"slug": "my-app",
|
|
60
|
+
"version": "1.0.0",
|
|
61
|
+
"scheme": "myapp",
|
|
62
|
+
"orientation": "portrait",
|
|
63
|
+
"icon": "./assets/images/icon.png",
|
|
64
|
+
"splash": {
|
|
65
|
+
"image": "./assets/images/splash.png",
|
|
66
|
+
"resizeMode": "contain",
|
|
67
|
+
"backgroundColor": "#ffffff"
|
|
68
|
+
},
|
|
69
|
+
"ios": {
|
|
70
|
+
"supportsTablet": true,
|
|
71
|
+
"bundleIdentifier": "com.company.myapp"
|
|
72
|
+
},
|
|
73
|
+
"android": {
|
|
74
|
+
"adaptiveIcon": {
|
|
75
|
+
"foregroundImage": "./assets/images/adaptive-icon.png",
|
|
76
|
+
"backgroundColor": "#ffffff"
|
|
77
|
+
},
|
|
78
|
+
"package": "com.company.myapp"
|
|
79
|
+
},
|
|
80
|
+
"plugins": [
|
|
81
|
+
"expo-router"
|
|
82
|
+
],
|
|
83
|
+
"experiments": {
|
|
84
|
+
"typedRoutes": true
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## tsconfig.json
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"extends": "expo/tsconfig.base",
|
|
95
|
+
"compilerOptions": {
|
|
96
|
+
"strict": true,
|
|
97
|
+
"baseUrl": ".",
|
|
98
|
+
"paths": {
|
|
99
|
+
"@/*": ["./*"],
|
|
100
|
+
"@/components/*": ["components/*"],
|
|
101
|
+
"@/hooks/*": ["hooks/*"],
|
|
102
|
+
"@/services/*": ["services/*"],
|
|
103
|
+
"@/stores/*": ["stores/*"],
|
|
104
|
+
"@/types/*": ["types/*"]
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## babel.config.js
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
module.exports = function (api) {
|
|
115
|
+
api.cache(true);
|
|
116
|
+
return {
|
|
117
|
+
presets: ['babel-preset-expo'],
|
|
118
|
+
plugins: [
|
|
119
|
+
[
|
|
120
|
+
'module-resolver',
|
|
121
|
+
{
|
|
122
|
+
root: ['.'],
|
|
123
|
+
alias: {
|
|
124
|
+
'@': '.',
|
|
125
|
+
'@/components': './components',
|
|
126
|
+
'@/hooks': './hooks',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
'react-native-reanimated/plugin', // Must be last
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Essential Dependencies
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"dependencies": {
|
|
141
|
+
"expo": "~50.0.0",
|
|
142
|
+
"expo-router": "~3.4.0",
|
|
143
|
+
"react-native-safe-area-context": "4.8.2",
|
|
144
|
+
"react-native-screens": "~3.29.0",
|
|
145
|
+
"@react-navigation/native": "^6.1.0",
|
|
146
|
+
"react-native-reanimated": "~3.6.0",
|
|
147
|
+
"react-native-gesture-handler": "~2.14.0",
|
|
148
|
+
"zustand": "^4.5.0",
|
|
149
|
+
"@tanstack/react-query": "^5.0.0",
|
|
150
|
+
"expo-image": "~1.10.0",
|
|
151
|
+
"react-native-mmkv": "^2.11.0"
|
|
152
|
+
},
|
|
153
|
+
"devDependencies": {
|
|
154
|
+
"@types/react": "~18.2.0",
|
|
155
|
+
"typescript": "^5.3.0"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Quick Reference
|
|
161
|
+
|
|
162
|
+
| Directory | Purpose |
|
|
163
|
+
|-----------|---------|
|
|
164
|
+
| `app/` | File-based routes |
|
|
165
|
+
| `components/ui/` | Reusable UI |
|
|
166
|
+
| `components/features/` | Feature components |
|
|
167
|
+
| `hooks/` | Custom hooks |
|
|
168
|
+
| `services/` | API, auth services |
|
|
169
|
+
| `stores/` | State management |
|
|
170
|
+
| `constants/` | App constants |
|
|
171
|
+
| `types/` | TypeScript types |
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Storage & Hooks
|
|
2
|
+
|
|
3
|
+
## AsyncStorage
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
7
|
+
|
|
8
|
+
// Basic operations
|
|
9
|
+
await AsyncStorage.setItem('user', JSON.stringify(user));
|
|
10
|
+
const user = JSON.parse(await AsyncStorage.getItem('user') || 'null');
|
|
11
|
+
await AsyncStorage.removeItem('user');
|
|
12
|
+
await AsyncStorage.clear();
|
|
13
|
+
|
|
14
|
+
// Multiple items
|
|
15
|
+
await AsyncStorage.multiSet([
|
|
16
|
+
['user', JSON.stringify(user)],
|
|
17
|
+
['settings', JSON.stringify(settings)],
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const values = await AsyncStorage.multiGet(['user', 'settings']);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## useStorage Hook
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
27
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
28
|
+
|
|
29
|
+
function useStorage<T>(key: string, initialValue: T) {
|
|
30
|
+
const [value, setValue] = useState<T>(initialValue);
|
|
31
|
+
const [loading, setLoading] = useState(true);
|
|
32
|
+
|
|
33
|
+
// Load on mount
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
AsyncStorage.getItem(key)
|
|
36
|
+
.then((item) => {
|
|
37
|
+
if (item !== null) {
|
|
38
|
+
setValue(JSON.parse(item));
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
.finally(() => setLoading(false));
|
|
42
|
+
}, [key]);
|
|
43
|
+
|
|
44
|
+
// Persist changes
|
|
45
|
+
const setStoredValue = useCallback(
|
|
46
|
+
async (newValue: T | ((prev: T) => T)) => {
|
|
47
|
+
const valueToStore =
|
|
48
|
+
newValue instanceof Function ? newValue(value) : newValue;
|
|
49
|
+
setValue(valueToStore);
|
|
50
|
+
await AsyncStorage.setItem(key, JSON.stringify(valueToStore));
|
|
51
|
+
},
|
|
52
|
+
[key, value]
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const removeValue = useCallback(async () => {
|
|
56
|
+
setValue(initialValue);
|
|
57
|
+
await AsyncStorage.removeItem(key);
|
|
58
|
+
}, [key, initialValue]);
|
|
59
|
+
|
|
60
|
+
return { value, setValue: setStoredValue, removeValue, loading };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Usage
|
|
64
|
+
function Settings() {
|
|
65
|
+
const { value: theme, setValue: setTheme, loading } = useStorage('theme', 'light');
|
|
66
|
+
|
|
67
|
+
if (loading) return <Loading />;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<Switch
|
|
71
|
+
value={theme === 'dark'}
|
|
72
|
+
onValueChange={(dark) => setTheme(dark ? 'dark' : 'light')}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## MMKV (Faster Alternative)
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { MMKV } from 'react-native-mmkv';
|
|
82
|
+
|
|
83
|
+
const storage = new MMKV();
|
|
84
|
+
|
|
85
|
+
// Synchronous operations
|
|
86
|
+
storage.set('user.name', 'John');
|
|
87
|
+
const name = storage.getString('user.name');
|
|
88
|
+
|
|
89
|
+
storage.set('user.age', 25);
|
|
90
|
+
const age = storage.getNumber('user.age');
|
|
91
|
+
|
|
92
|
+
storage.set('user.premium', true);
|
|
93
|
+
const isPremium = storage.getBoolean('user.premium');
|
|
94
|
+
|
|
95
|
+
storage.delete('user.name');
|
|
96
|
+
storage.clearAll();
|
|
97
|
+
|
|
98
|
+
// JSON data
|
|
99
|
+
storage.set('user', JSON.stringify(user));
|
|
100
|
+
const user = JSON.parse(storage.getString('user') || '{}');
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## useMMKV Hook
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { useMMKVString, useMMKVNumber, useMMKVBoolean } from 'react-native-mmkv';
|
|
107
|
+
|
|
108
|
+
function Settings() {
|
|
109
|
+
const [theme, setTheme] = useMMKVString('theme');
|
|
110
|
+
const [fontSize, setFontSize] = useMMKVNumber('fontSize');
|
|
111
|
+
const [notifications, setNotifications] = useMMKVBoolean('notifications');
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<>
|
|
115
|
+
<Switch
|
|
116
|
+
value={theme === 'dark'}
|
|
117
|
+
onValueChange={(dark) => setTheme(dark ? 'dark' : 'light')}
|
|
118
|
+
/>
|
|
119
|
+
<Slider value={fontSize} onValueChange={setFontSize} />
|
|
120
|
+
<Switch value={notifications} onValueChange={setNotifications} />
|
|
121
|
+
</>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Zustand with MMKV
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { create } from 'zustand';
|
|
130
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
131
|
+
import { MMKV } from 'react-native-mmkv';
|
|
132
|
+
|
|
133
|
+
const storage = new MMKV();
|
|
134
|
+
|
|
135
|
+
const mmkvStorage = {
|
|
136
|
+
getItem: (name: string) => storage.getString(name) ?? null,
|
|
137
|
+
setItem: (name: string, value: string) => storage.set(name, value),
|
|
138
|
+
removeItem: (name: string) => storage.delete(name),
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
interface SettingsStore {
|
|
142
|
+
theme: 'light' | 'dark';
|
|
143
|
+
setTheme: (theme: 'light' | 'dark') => void;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const useSettingsStore = create<SettingsStore>()(
|
|
147
|
+
persist(
|
|
148
|
+
(set) => ({
|
|
149
|
+
theme: 'light',
|
|
150
|
+
setTheme: (theme) => set({ theme }),
|
|
151
|
+
}),
|
|
152
|
+
{
|
|
153
|
+
name: 'settings-storage',
|
|
154
|
+
storage: createJSONStorage(() => mmkvStorage),
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Quick Reference
|
|
161
|
+
|
|
162
|
+
| Storage | Speed | Async | Use Case |
|
|
163
|
+
|---------|-------|-------|----------|
|
|
164
|
+
| AsyncStorage | Slow | Yes | Small data, simple apps |
|
|
165
|
+
| MMKV | Fast | No | Large data, frequent access |
|
|
166
|
+
| SecureStore | Medium | Yes | Sensitive data (tokens) |
|
|
167
|
+
|
|
168
|
+
| Hook | Returns |
|
|
169
|
+
|------|---------|
|
|
170
|
+
| `useStorage()` | { value, setValue, loading } |
|
|
171
|
+
| `useMMKVString()` | [value, setValue] |
|
|
172
|
+
| `useMMKVNumber()` | [value, setValue] |
|
|
173
|
+
| `useMMKVBoolean()` | [value, setValue] |
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jacek Pudysz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Attribution
|
|
2
|
+
|
|
3
|
+
This skill is copied from Jacek Pudysz's public `jpudysz/react-native-unistyles` repository.
|
|
4
|
+
|
|
5
|
+
- Source: https://github.com/jpudysz/react-native-unistyles/tree/main/skills/react-native-unistyles-v3
|
|
6
|
+
- Imported from commit: `8b5e9fd281a81bdfd87d4fe9e6a0b042c84c5c83`
|
|
7
|
+
- Upstream skill name: `react-native-unistyles-v3`
|
|
8
|
+
- License: MIT, as declared by the upstream README and `packages/unistyles/package.json`
|
|
9
|
+
- Copyright: Copyright (c) 2026 Jacek Pudysz
|
|
10
|
+
|
|
11
|
+
The upstream repository does not include a root `LICENSE` file in the imported commit, so this vendored copy includes the standard MIT license text in `LICENSE` with the upstream author attribution.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-native-unistyles-v3
|
|
3
|
+
description: Guide for using react-native-unistyles v3. Triggers on unistyles, StyleSheet.create, create stylesheet, add theme, breakpoints, variants, withUnistyles, useUnistyles, ScopedTheme, UnistylesRuntime, responsive styles, media queries, dynamic styles, scoped theme, adaptive theme, setup, config, theming, variants, web features and troubleshooting.
|
|
4
|
+
disable-model-invocation: false
|
|
5
|
+
user-invocable: true
|
|
6
|
+
allowed-tools: Read, Grep, Glob, Edit, Write, Bash(npx *)
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# React Native Unistyles v3 Skill
|
|
10
|
+
|
|
11
|
+
You are assisting with React Native Unistyles v3 styling. v3 is a zero-re-render styling library with a C++ core (Nitro Modules) and Babel plugin that processes StyleSheets at build time. It replaces React Native's `StyleSheet` with a reactive, theme-aware, responsive system.
|
|
12
|
+
|
|
13
|
+
## Prerequisites
|
|
14
|
+
|
|
15
|
+
- React Native 0.78+ with **New Architecture mandatory** (default from RN 0.83+)
|
|
16
|
+
- React 19+ (enforced at runtime)
|
|
17
|
+
- `react-native-nitro-modules` (native bridge dependency)
|
|
18
|
+
- `react-native-edge-to-edge` (required for Android edge-to-edge insets)
|
|
19
|
+
- Expo SDK 53+ (if using Expo; **not compatible with Expo Go** — requires dev client or prebuild)
|
|
20
|
+
- Xcode 16+ (iOS)
|
|
21
|
+
|
|
22
|
+
## Workflow
|
|
23
|
+
|
|
24
|
+
1. **Read user's code first** — understand current setup, imports, and styling approach
|
|
25
|
+
2. **Identify intent** — new setup, add theming, responsive design, fix an issue, etc.
|
|
26
|
+
3. **Apply v3 patterns** — use the correct API from this skill; consult reference files for details
|
|
27
|
+
4. **Verify correctness** — check for critical rule violations (spreading, barrel re-exports, etc.)
|
|
28
|
+
|
|
29
|
+
## Quick Reference
|
|
30
|
+
|
|
31
|
+
### StyleSheet.create — 3 overloads
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { StyleSheet } from 'react-native-unistyles'
|
|
35
|
+
|
|
36
|
+
// 1. Static object compatible with React Native StyleSheet.create
|
|
37
|
+
const styles = StyleSheet.create({
|
|
38
|
+
container: { flex: 1, padding: 16 }
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// 2. Theme function (reactive to theme changes, zero re-renders)
|
|
42
|
+
const styles = StyleSheet.create(theme => ({
|
|
43
|
+
container: { backgroundColor: theme.colors.background }
|
|
44
|
+
}))
|
|
45
|
+
|
|
46
|
+
// 3. Theme + miniRuntime function (reactive to theme AND device state)
|
|
47
|
+
const styles = StyleSheet.create((theme, rt) => ({
|
|
48
|
+
container: {
|
|
49
|
+
backgroundColor: theme.colors.background,
|
|
50
|
+
paddingTop: rt.insets.top,
|
|
51
|
+
width: rt.screen.width > 768 ? 500 : rt.screen.width - 32
|
|
52
|
+
}
|
|
53
|
+
}))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### StyleSheet.configure — one-time setup
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
StyleSheet.configure({
|
|
60
|
+
themes: { light: lightTheme, dark: darkTheme },
|
|
61
|
+
breakpoints: { xs: 0, sm: 576, md: 768, lg: 992, xl: 1200 },
|
|
62
|
+
settings: {
|
|
63
|
+
initialTheme: 'light', // or: () => storage.getString('theme') ?? 'light'
|
|
64
|
+
// adaptiveThemes: true, // auto-switch light/dark based on OS (mutually exclusive with initialTheme)
|
|
65
|
+
// CSSVars: true, // use CSS custom properties on web
|
|
66
|
+
// nativeBreakpointsMode: 'pixels' | 'points'
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Variants
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
const styles = StyleSheet.create(theme => ({
|
|
75
|
+
button: {
|
|
76
|
+
variants: {
|
|
77
|
+
size: {
|
|
78
|
+
small: { padding: 4 },
|
|
79
|
+
medium: { padding: 8 },
|
|
80
|
+
large: { padding: 16 },
|
|
81
|
+
},
|
|
82
|
+
color: {
|
|
83
|
+
primary: { backgroundColor: theme.colors.primary },
|
|
84
|
+
secondary: { backgroundColor: theme.colors.secondary },
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
compoundVariants: [
|
|
88
|
+
{ size: 'large', color: 'primary', styles: { borderWidth: 2 } }
|
|
89
|
+
],
|
|
90
|
+
}
|
|
91
|
+
}))
|
|
92
|
+
|
|
93
|
+
// In component — call useVariants BEFORE accessing styles
|
|
94
|
+
styles.useVariants({ size: 'large', color: 'primary' })
|
|
95
|
+
return <View style={styles.button} />
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### withUnistyles — wrap third-party components (not React Native or Reanimated components)
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { withUnistyles } from 'react-native-unistyles'
|
|
102
|
+
import { Button } from 'some-library'
|
|
103
|
+
|
|
104
|
+
const UniButton = withUnistyles(Button, (theme, rt) => ({
|
|
105
|
+
color: theme.colors.primary,
|
|
106
|
+
size: rt.screen.width > 400 ? 'large' : 'small'
|
|
107
|
+
}))
|
|
108
|
+
|
|
109
|
+
// Usage: <UniButton title="Press me" />
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Critical Rules
|
|
113
|
+
|
|
114
|
+
1. **NEVER spread styles** — `{...styles.x}` breaks C++ proxy binding. ALWAYS use array syntax: `[styles.x, styles.y]` or `[styles.x, { marginTop: 10 }]`
|
|
115
|
+
2. **NEVER re-export StyleSheet from barrel files** — the Babel plugin detects `StyleSheet.create` by import source. Re-exporting breaks detection.
|
|
116
|
+
3. **Babel plugin is REQUIRED** — add `['react-native-unistyles/plugin', { root: 'src' }]` to babel.config.js. Without it, styles won't be reactive.
|
|
117
|
+
4. **Import StyleSheet from `react-native-unistyles` only** — it polyfills all RN StyleSheet APIs (`hairlineWidth`, `compose`, `flatten`, `absoluteFill`). Replace all `import { StyleSheet } from 'react-native'`.
|
|
118
|
+
5. **`styles.useVariants()` must be called before accessing variant-dependent styles** — treat it like a React hook (top of component).
|
|
119
|
+
6. **Prefer `StyleSheet.create(theme => ...)` over `useUnistyles()`** — theme functions cause zero re-renders; `useUnistyles()` re-renders on every theme/runtime change.
|
|
120
|
+
7. **`StyleSheet.configure()` must be called before any `StyleSheet.create()`** — typically in your app entry point, before any component renders.
|
|
121
|
+
|
|
122
|
+
## Decision Trees
|
|
123
|
+
|
|
124
|
+
### Choosing a styling approach
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
Need theme/runtime in styles?
|
|
128
|
+
├─ YES → StyleSheet.create((theme, rt) => ...) [zero re-renders]
|
|
129
|
+
└─ NO → StyleSheet.create({ ... }) [static styles]
|
|
130
|
+
|
|
131
|
+
Need theme values as non-style props?
|
|
132
|
+
├─ YES → withUnistyles(Component, (theme, rt) => ({ propName: theme.value }))
|
|
133
|
+
└─ NO → Pass Unistyles styles via style prop directly
|
|
134
|
+
|
|
135
|
+
Need theme/runtime in component logic (not just styles)?
|
|
136
|
+
├─ YES → useUnistyles() hook [causes re-renders]
|
|
137
|
+
└─ NO → Use StyleSheet.create with theme function
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Third-party component integration
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
Does the component accept a `style` prop?
|
|
144
|
+
├─ YES → Pass Unistyles styles directly (Babel auto-processes standard RN views)
|
|
145
|
+
│ If it's a custom native view → add to autoProcessPaths in Babel config
|
|
146
|
+
├─ NO → Does it accept theme-derived props (color, size, etc.)?
|
|
147
|
+
│ ├─ YES → withUnistyles(Component, (theme, rt) => ({ prop: theme.value }))
|
|
148
|
+
│ └─ NO → useUnistyles() as fallback
|
|
149
|
+
└─ Component from node_modules not processed?
|
|
150
|
+
→ Add its path to autoProcessPaths or autoProcessImports in Babel plugin config
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Reference Files
|
|
154
|
+
|
|
155
|
+
- **Setup, installation, Babel plugin, TypeScript, testing, Expo Router**: read [references/setup-guide.md](references/setup-guide.md)
|
|
156
|
+
- **Complete API for every export**: read [references/api-reference.md](references/api-reference.md)
|
|
157
|
+
- **Code patterns: themes, variants, breakpoints, web, SSR, Reanimated**: read [references/styling-patterns.md](references/styling-patterns.md)
|
|
158
|
+
- **withUnistyles, autoProcessPaths, React Compiler, Reanimated, FlatList**: read [references/third-party-integration.md](references/third-party-integration.md)
|
|
159
|
+
- **Troubleshooting from 150+ GitHub issues with solutions**: read [references/common-issues.md](references/common-issues.md)
|