@ranimontagna/agent-toolkit 0.1.5 → 0.1.7
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 +68 -14
- package/package.json +1 -1
- package/skills/backend/go/golang-patterns/LICENSE +21 -0
- package/skills/backend/go/golang-patterns/NOTICE.md +10 -0
- package/skills/backend/go/golang-patterns/SKILL.md +674 -0
- package/skills/backend/go/golang-testing/LICENSE +21 -0
- package/skills/backend/go/golang-testing/NOTICE.md +10 -0
- package/skills/backend/go/golang-testing/SKILL.md +329 -0
- package/skills/backend/java/java-coding-standards/LICENSE +21 -0
- package/skills/backend/java/java-coding-standards/NOTICE.md +10 -0
- package/skills/backend/java/java-coding-standards/SKILL.md +383 -0
- package/skills/backend/java/java-junit/LICENSE +21 -0
- package/skills/backend/java/java-junit/NOTICE.md +10 -0
- package/skills/backend/java/java-junit/SKILL.md +64 -0
- 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-patterns/rules/react/LICENSE +21 -0
- package/skills/frontend/react/react-patterns/rules/react/NOTICE.md +11 -0
- package/skills/frontend/react/react-patterns/rules/react/coding-style.md +109 -0
- package/skills/frontend/react/react-patterns/rules/react/hooks.md +187 -0
- package/skills/frontend/react/react-patterns/rules/react/patterns.md +194 -0
- package/skills/frontend/react/react-patterns/rules/react/security.md +180 -0
- package/skills/frontend/react/react-patterns/rules/react/testing.md +208 -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-performance/rules/react/LICENSE +21 -0
- package/skills/frontend/react/react-performance/rules/react/NOTICE.md +11 -0
- package/skills/frontend/react/react-performance/rules/react/coding-style.md +109 -0
- package/skills/frontend/react/react-performance/rules/react/hooks.md +187 -0
- package/skills/frontend/react/react-performance/rules/react/patterns.md +194 -0
- package/skills/frontend/react/react-performance/rules/react/security.md +180 -0
- package/skills/frontend/react/react-performance/rules/react/testing.md +208 -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/react-testing/rules/react/LICENSE +21 -0
- package/skills/frontend/react/react-testing/rules/react/NOTICE.md +11 -0
- package/skills/frontend/react/react-testing/rules/react/coding-style.md +109 -0
- package/skills/frontend/react/react-testing/rules/react/hooks.md +187 -0
- package/skills/frontend/react/react-testing/rules/react/patterns.md +194 -0
- package/skills/frontend/react/react-testing/rules/react/security.md +180 -0
- package/skills/frontend/react/react-testing/rules/react/testing.md +208 -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,208 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.test.tsx"
|
|
4
|
+
- "**/*.test.jsx"
|
|
5
|
+
- "**/*.spec.tsx"
|
|
6
|
+
- "**/*.spec.jsx"
|
|
7
|
+
- "**/__tests__/**/*.ts"
|
|
8
|
+
- "**/__tests__/**/*.tsx"
|
|
9
|
+
---
|
|
10
|
+
# React Testing
|
|
11
|
+
|
|
12
|
+
> This file extends the upstream `typescript/testing.md` and `common/testing.md` rules with React specific content.
|
|
13
|
+
|
|
14
|
+
## Library Choice
|
|
15
|
+
|
|
16
|
+
- **React Testing Library (RTL)** — the standard for component testing. Tests behavior through the rendered DOM.
|
|
17
|
+
- **Vitest** — preferred runner for new Vite-based projects. Faster than Jest, native ESM, same API.
|
|
18
|
+
- **Jest** — still the default for Next.js / CRA projects. RTL works identically.
|
|
19
|
+
- **Playwright Component Testing** — when component tests need a real browser engine (animation, layout, complex events)
|
|
20
|
+
- **Cypress Component Testing** — alternative real-browser component runner
|
|
21
|
+
|
|
22
|
+
Pick one component test runner per project — do not mix RTL + Playwright CT in the same repo.
|
|
23
|
+
|
|
24
|
+
## Core Principle
|
|
25
|
+
|
|
26
|
+
Test what the user sees and does, not implementation details.
|
|
27
|
+
|
|
28
|
+
- Query by accessible role first, then label, then text — fall back to `data-testid` only when nothing else fits
|
|
29
|
+
- Never assert on internal state, props passed to children, or which hooks were called
|
|
30
|
+
- Refactor without breaking tests = the test was testing behavior; that is the goal
|
|
31
|
+
|
|
32
|
+
## Query Priority
|
|
33
|
+
|
|
34
|
+
RTL exposes queries in three families. Use this priority order top-down:
|
|
35
|
+
|
|
36
|
+
1. **Accessible to everyone**
|
|
37
|
+
- `getByRole(role, { name })` — primary choice
|
|
38
|
+
- `getByLabelText` — for form inputs
|
|
39
|
+
- `getByPlaceholderText` — when no label is available (and add a label)
|
|
40
|
+
- `getByText` — for non-interactive text
|
|
41
|
+
- `getByDisplayValue` — for form fields with a current value
|
|
42
|
+
|
|
43
|
+
2. **Semantic queries**
|
|
44
|
+
- `getByAltText` — for images
|
|
45
|
+
- `getByTitle` — last resort, low accessibility value
|
|
46
|
+
|
|
47
|
+
3. **Test IDs**
|
|
48
|
+
- `getByTestId("some-id")` — escape hatch only, when none of the above work
|
|
49
|
+
|
|
50
|
+
`getBy*` throws when no match. `queryBy*` returns null (use for asserting absence). `findBy*` returns a promise (use for async).
|
|
51
|
+
|
|
52
|
+
## User Interaction
|
|
53
|
+
|
|
54
|
+
Prefer `userEvent` over `fireEvent`. `userEvent` simulates real browser sequences (focus, keydown, beforeinput, input, keyup) — `fireEvent` dispatches a single synthetic event.
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import userEvent from "@testing-library/user-event";
|
|
58
|
+
|
|
59
|
+
test("submits the form", async () => {
|
|
60
|
+
const user = userEvent.setup();
|
|
61
|
+
render(<UserForm onSubmit={handleSubmit} />);
|
|
62
|
+
|
|
63
|
+
await user.type(screen.getByLabelText("Email"), "user@example.com");
|
|
64
|
+
await user.click(screen.getByRole("button", { name: /save/i }));
|
|
65
|
+
|
|
66
|
+
expect(handleSubmit).toHaveBeenCalledWith({ email: "user@example.com" });
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- Always `await` `userEvent` calls — they are async
|
|
71
|
+
- Call `userEvent.setup()` once at the top of each test, then reuse the returned `user`
|
|
72
|
+
|
|
73
|
+
## Async Assertions
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
// WRONG: synchronous query for async-rendered content
|
|
77
|
+
expect(screen.getByText("Loaded")).toBeInTheDocument(); // throws — not in DOM yet
|
|
78
|
+
|
|
79
|
+
// CORRECT: findBy* (returns a promise, retries)
|
|
80
|
+
expect(await screen.findByText("Loaded")).toBeInTheDocument();
|
|
81
|
+
|
|
82
|
+
// CORRECT: waitFor for non-element assertions
|
|
83
|
+
await waitFor(() => expect(saveSpy).toHaveBeenCalled());
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- `findBy*` for async element appearance
|
|
87
|
+
- `waitFor` for async expectations on side effects or other matchers
|
|
88
|
+
- Never `setTimeout` + assertion — flaky
|
|
89
|
+
|
|
90
|
+
## Network Mocking with MSW
|
|
91
|
+
|
|
92
|
+
Use Mock Service Worker for any test that hits a network boundary. MSW runs at the network layer, so the component, hooks, and fetch library all behave as in production.
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
// test setup
|
|
96
|
+
import { setupServer } from "msw/node";
|
|
97
|
+
import { http, HttpResponse } from "msw";
|
|
98
|
+
|
|
99
|
+
const server = setupServer(
|
|
100
|
+
http.get("/api/users/:id", ({ params }) =>
|
|
101
|
+
HttpResponse.json({ id: params.id, name: "Alice" }),
|
|
102
|
+
),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
beforeAll(() => server.listen());
|
|
106
|
+
afterEach(() => server.resetHandlers());
|
|
107
|
+
afterAll(() => server.close());
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Per-test override:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
test("renders error on 500", async () => {
|
|
114
|
+
server.use(http.get("/api/users/:id", () => new HttpResponse(null, { status: 500 })));
|
|
115
|
+
render(<UserPage id="1" />);
|
|
116
|
+
expect(await screen.findByText(/something went wrong/i)).toBeInTheDocument();
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Avoid Snapshot Tests for Components
|
|
121
|
+
|
|
122
|
+
Snapshots of rendered output are brittle, hard to review, and rubber-stamped by reviewers. Use them only for:
|
|
123
|
+
|
|
124
|
+
- Pure data serialization (e.g., a transformer that produces a stable string)
|
|
125
|
+
- Catching unintended regressions in non-visual output
|
|
126
|
+
|
|
127
|
+
For component visual regression, use Playwright / Cypress / Percy screenshots — actual visual diffs, not DOM diffs.
|
|
128
|
+
|
|
129
|
+
## Test Setup Helpers
|
|
130
|
+
|
|
131
|
+
Wrap providers once:
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
function renderWithProviders(ui: React.ReactElement) {
|
|
135
|
+
return render(
|
|
136
|
+
<QueryClientProvider client={new QueryClient()}>
|
|
137
|
+
<ThemeProvider theme={lightTheme}>
|
|
138
|
+
<Router>{ui}</Router>
|
|
139
|
+
</ThemeProvider>
|
|
140
|
+
</QueryClientProvider>,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Export from `test-utils.tsx` and use everywhere.
|
|
146
|
+
|
|
147
|
+
## Custom Hook Testing
|
|
148
|
+
|
|
149
|
+
Use `renderHook` from RTL:
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
import { renderHook, act } from "@testing-library/react";
|
|
153
|
+
|
|
154
|
+
test("useCounter increments", () => {
|
|
155
|
+
const { result } = renderHook(() => useCounter());
|
|
156
|
+
act(() => result.current.increment());
|
|
157
|
+
expect(result.current.count).toBe(1);
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- Always wrap state-changing calls in `act`
|
|
162
|
+
- Always test through the public hook API, not internal implementation
|
|
163
|
+
|
|
164
|
+
## Accessibility Assertions
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
import { axe } from "vitest-axe"; // or jest-axe
|
|
168
|
+
|
|
169
|
+
test("UserCard has no a11y violations", async () => {
|
|
170
|
+
const { container } = render(<UserCard user={mockUser} />);
|
|
171
|
+
expect(await axe(container)).toHaveNoViolations();
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Run axe assertions in component tests — catches missing labels, ARIA misuse, color contrast (limited).
|
|
176
|
+
|
|
177
|
+
## When to Reach for Playwright / Cypress
|
|
178
|
+
|
|
179
|
+
Component test with RTL + JSDOM cannot:
|
|
180
|
+
|
|
181
|
+
- Test real layout (flexbox, grid, viewport-dependent rendering)
|
|
182
|
+
- Test scrolling, drag-and-drop, paste from clipboard
|
|
183
|
+
- Test browser-native animation, CSS transitions
|
|
184
|
+
- Test cross-frame interactions (iframes, popups)
|
|
185
|
+
|
|
186
|
+
For those, use Playwright Component Testing or end-to-end Playwright/Cypress runs. See the e2e-testing skill when it is installed.
|
|
187
|
+
|
|
188
|
+
## Coverage Targets
|
|
189
|
+
|
|
190
|
+
| Layer | Target |
|
|
191
|
+
|---|---|
|
|
192
|
+
| Pure utility functions | ≥90% |
|
|
193
|
+
| Custom hooks | ≥85% |
|
|
194
|
+
| Components (presentational) | ≥80% — behavior, not lines |
|
|
195
|
+
| Container components | ≥70% — golden paths + error states |
|
|
196
|
+
| Pages (E2E covered separately) | Smoke test per route minimum |
|
|
197
|
+
|
|
198
|
+
## Anti-Patterns
|
|
199
|
+
|
|
200
|
+
- Asserting on `container.querySelector` — bypasses accessibility queries
|
|
201
|
+
- Asserting on number of renders — implementation detail
|
|
202
|
+
- Mocking React hooks (`jest.mock("react", ...)`) — refactor the component instead
|
|
203
|
+
- Mocking child components by default — tests the integration, not the parent in isolation
|
|
204
|
+
- Manual `act()` warnings ignored — they indicate real bugs
|
|
205
|
+
|
|
206
|
+
## Skill Reference
|
|
207
|
+
|
|
208
|
+
See `skills/react-testing/SKILL.md` for end-to-end test examples, MSW patterns, and accessibility test scaffolding.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
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 Jeffallan's public `Jeffallan/claude-skills` repository.
|
|
4
|
+
|
|
5
|
+
- Source: https://github.com/Jeffallan/claude-skills/tree/main/skills/react-native-expert
|
|
6
|
+
- Imported from commit: `e8be415bc94d8d6ebddc2fb50e5d03c6e27d4319`
|
|
7
|
+
- Upstream skill name: `react-native-expert`
|
|
8
|
+
- License: MIT
|
|
9
|
+
- Copyright: Copyright (c) 2025
|
|
10
|
+
|
|
11
|
+
The upstream MIT license text is preserved in `LICENSE`.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-native-expert
|
|
3
|
+
description: Builds, optimizes, and debugs cross-platform mobile applications with React Native and Expo. Implements navigation hierarchies (tabs, stacks, drawers), configures native modules, optimizes FlatList rendering with memo and useCallback, and handles platform-specific code for iOS and Android. Use when building a React Native or Expo mobile app, setting up navigation, integrating native modules, improving scroll performance, handling SafeArea or keyboard input, or configuring Expo SDK projects.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: https://github.com/Jeffallan
|
|
7
|
+
version: "1.1.0"
|
|
8
|
+
domain: frontend
|
|
9
|
+
triggers: React Native, Expo, mobile app, iOS, Android, cross-platform, native module
|
|
10
|
+
role: specialist
|
|
11
|
+
scope: implementation
|
|
12
|
+
output-format: code
|
|
13
|
+
related-skills: react-expert, flutter-expert, test-master
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# React Native Expert
|
|
17
|
+
|
|
18
|
+
Senior mobile engineer building production-ready cross-platform applications with React Native and Expo.
|
|
19
|
+
|
|
20
|
+
## Core Workflow
|
|
21
|
+
|
|
22
|
+
1. **Setup** — Expo Router or React Navigation, TypeScript config → _run `npx expo doctor` to verify environment and SDK compatibility; fix any reported issues before proceeding_
|
|
23
|
+
2. **Structure** — Feature-based organization
|
|
24
|
+
3. **Implement** — Components with platform handling → _verify on iOS simulator and Android emulator; check Metro bundler output for errors before moving on_
|
|
25
|
+
4. **Optimize** — FlatList, images, memory → _profile with Flipper or React DevTools_
|
|
26
|
+
5. **Test** — Both platforms, real devices
|
|
27
|
+
|
|
28
|
+
### Error Recovery
|
|
29
|
+
- **Metro bundler errors** → clear cache with `npx expo start --clear`, then restart
|
|
30
|
+
- **iOS build fails** → check Xcode logs → resolve native dependency or provisioning issue → rebuild with `npx expo run:ios`
|
|
31
|
+
- **Android build fails** → check `adb logcat` or Gradle output → resolve SDK/NDK version mismatch → rebuild with `npx expo run:android`
|
|
32
|
+
- **Native module not found** → run `npx expo install <module>` to ensure compatible version, then rebuild native layers
|
|
33
|
+
|
|
34
|
+
## Reference Guide
|
|
35
|
+
|
|
36
|
+
Load detailed guidance based on context:
|
|
37
|
+
|
|
38
|
+
| Topic | Reference | Load When |
|
|
39
|
+
|-------|-----------|-----------|
|
|
40
|
+
| Navigation | `references/expo-router.md` | Expo Router, tabs, stacks, deep linking |
|
|
41
|
+
| Platform | `references/platform-handling.md` | iOS/Android code, SafeArea, keyboard |
|
|
42
|
+
| Lists | `references/list-optimization.md` | FlatList, performance, memo |
|
|
43
|
+
| Storage | `references/storage-hooks.md` | AsyncStorage, MMKV, persistence |
|
|
44
|
+
| Structure | `references/project-structure.md` | Project setup, architecture |
|
|
45
|
+
|
|
46
|
+
## Constraints
|
|
47
|
+
|
|
48
|
+
### MUST DO
|
|
49
|
+
- Use FlatList/SectionList for lists (not ScrollView)
|
|
50
|
+
- Implement memo + useCallback for list items
|
|
51
|
+
- Handle SafeAreaView for notches
|
|
52
|
+
- Test on both iOS and Android real devices
|
|
53
|
+
- Use KeyboardAvoidingView for forms
|
|
54
|
+
- Handle Android back button in navigation
|
|
55
|
+
|
|
56
|
+
### MUST NOT DO
|
|
57
|
+
- Use ScrollView for large lists
|
|
58
|
+
- Use inline styles extensively (creates new objects)
|
|
59
|
+
- Hardcode dimensions (use Dimensions API or flex)
|
|
60
|
+
- Ignore memory leaks from subscriptions
|
|
61
|
+
- Skip platform-specific testing
|
|
62
|
+
- Use waitFor/setTimeout for animations (use Reanimated)
|
|
63
|
+
|
|
64
|
+
## Code Examples
|
|
65
|
+
|
|
66
|
+
### Optimized FlatList with memo + useCallback
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import React, { memo, useCallback } from 'react';
|
|
70
|
+
import { FlatList, View, Text, StyleSheet } from 'react-native';
|
|
71
|
+
|
|
72
|
+
type Item = { id: string; title: string };
|
|
73
|
+
|
|
74
|
+
const ListItem = memo(({ title, onPress }: { title: string; onPress: () => void }) => (
|
|
75
|
+
<View style={styles.item}>
|
|
76
|
+
<Text onPress={onPress}>{title}</Text>
|
|
77
|
+
</View>
|
|
78
|
+
));
|
|
79
|
+
|
|
80
|
+
export function ItemList({ data }: { data: Item[] }) {
|
|
81
|
+
const handlePress = useCallback((id: string) => {
|
|
82
|
+
console.log('pressed', id);
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const renderItem = useCallback(
|
|
86
|
+
({ item }: { item: Item }) => (
|
|
87
|
+
<ListItem title={item.title} onPress={() => handlePress(item.id)} />
|
|
88
|
+
),
|
|
89
|
+
[handlePress]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<FlatList
|
|
94
|
+
data={data}
|
|
95
|
+
keyExtractor={(item) => item.id}
|
|
96
|
+
renderItem={renderItem}
|
|
97
|
+
removeClippedSubviews
|
|
98
|
+
maxToRenderPerBatch={10}
|
|
99
|
+
windowSize={5}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const styles = StyleSheet.create({
|
|
105
|
+
item: { padding: 16, borderBottomWidth: StyleSheet.hairlineWidth },
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### KeyboardAvoidingView Form
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import React from 'react';
|
|
113
|
+
import {
|
|
114
|
+
KeyboardAvoidingView,
|
|
115
|
+
Platform,
|
|
116
|
+
ScrollView,
|
|
117
|
+
TextInput,
|
|
118
|
+
StyleSheet,
|
|
119
|
+
SafeAreaView,
|
|
120
|
+
} from 'react-native';
|
|
121
|
+
|
|
122
|
+
export function LoginForm() {
|
|
123
|
+
return (
|
|
124
|
+
<SafeAreaView style={styles.safe}>
|
|
125
|
+
<KeyboardAvoidingView
|
|
126
|
+
style={styles.flex}
|
|
127
|
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
|
128
|
+
>
|
|
129
|
+
<ScrollView contentContainerStyle={styles.content} keyboardShouldPersistTaps="handled">
|
|
130
|
+
<TextInput style={styles.input} placeholder="Email" autoCapitalize="none" />
|
|
131
|
+
<TextInput style={styles.input} placeholder="Password" secureTextEntry />
|
|
132
|
+
</ScrollView>
|
|
133
|
+
</KeyboardAvoidingView>
|
|
134
|
+
</SafeAreaView>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const styles = StyleSheet.create({
|
|
139
|
+
safe: { flex: 1 },
|
|
140
|
+
flex: { flex: 1 },
|
|
141
|
+
content: { padding: 16, gap: 12 },
|
|
142
|
+
input: { borderWidth: 1, borderRadius: 8, padding: 12, fontSize: 16 },
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Platform-Specific Component
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { Platform, StyleSheet, View, Text } from 'react-native';
|
|
150
|
+
|
|
151
|
+
export function StatusChip({ label }: { label: string }) {
|
|
152
|
+
return (
|
|
153
|
+
<View style={styles.chip}>
|
|
154
|
+
<Text style={styles.label}>{label}</Text>
|
|
155
|
+
</View>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const styles = StyleSheet.create({
|
|
160
|
+
chip: {
|
|
161
|
+
paddingHorizontal: 12,
|
|
162
|
+
paddingVertical: 4,
|
|
163
|
+
borderRadius: 999,
|
|
164
|
+
backgroundColor: '#0a7ea4',
|
|
165
|
+
// Platform-specific shadow
|
|
166
|
+
...Platform.select({
|
|
167
|
+
ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.2, shadowRadius: 4 },
|
|
168
|
+
android: { elevation: 3 },
|
|
169
|
+
}),
|
|
170
|
+
},
|
|
171
|
+
label: { color: '#fff', fontSize: 13, fontWeight: '600' },
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Output Format
|
|
176
|
+
|
|
177
|
+
When implementing React Native features, deliver:
|
|
178
|
+
1. **Component code** — TypeScript, with prop types defined
|
|
179
|
+
2. **Platform handling** — `Platform.select` or `.ios.tsx` / `.android.tsx` splits as needed
|
|
180
|
+
3. **Navigation integration** — route params typed, back-button handling included
|
|
181
|
+
4. **Performance notes** — memo boundaries, key extractor strategy, image caching
|
|
182
|
+
|
|
183
|
+
## Knowledge Reference
|
|
184
|
+
|
|
185
|
+
React Native 0.73+, Expo SDK 50+, Expo Router, React Navigation 7, Reanimated 3, Gesture Handler, AsyncStorage, MMKV, React Query, Zustand
|
|
186
|
+
|
|
187
|
+
[Documentation](https://jeffallan.github.io/claude-skills/skills/frontend/react-native-expert/)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Expo Router
|
|
2
|
+
|
|
3
|
+
## Project Structure
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
app/
|
|
7
|
+
├── _layout.tsx # Root layout
|
|
8
|
+
├── index.tsx # Home (/)
|
|
9
|
+
├── +not-found.tsx # 404 page
|
|
10
|
+
├── (tabs)/ # Tab group
|
|
11
|
+
│ ├── _layout.tsx # Tab bar config
|
|
12
|
+
│ ├── index.tsx # First tab
|
|
13
|
+
│ └── profile.tsx # Profile tab
|
|
14
|
+
├── (auth)/ # Auth group (no tabs)
|
|
15
|
+
│ ├── _layout.tsx
|
|
16
|
+
│ ├── login.tsx
|
|
17
|
+
│ └── register.tsx
|
|
18
|
+
├── settings/
|
|
19
|
+
│ ├── _layout.tsx # Stack layout
|
|
20
|
+
│ ├── index.tsx # Settings main
|
|
21
|
+
│ └── notifications.tsx
|
|
22
|
+
└── details/[id].tsx # Dynamic route
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Root Layout
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// app/_layout.tsx
|
|
29
|
+
import { Stack } from 'expo-router';
|
|
30
|
+
import { ThemeProvider } from '@react-navigation/native';
|
|
31
|
+
|
|
32
|
+
export default function RootLayout() {
|
|
33
|
+
return (
|
|
34
|
+
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
|
35
|
+
<Stack screenOptions={{ headerShown: false }}>
|
|
36
|
+
<Stack.Screen name="(tabs)" />
|
|
37
|
+
<Stack.Screen name="(auth)" />
|
|
38
|
+
<Stack.Screen
|
|
39
|
+
name="details/[id]"
|
|
40
|
+
options={{ presentation: 'modal' }}
|
|
41
|
+
/>
|
|
42
|
+
</Stack>
|
|
43
|
+
</ThemeProvider>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Tab Layout
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// app/(tabs)/_layout.tsx
|
|
52
|
+
import { Tabs } from 'expo-router';
|
|
53
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
54
|
+
|
|
55
|
+
export default function TabLayout() {
|
|
56
|
+
return (
|
|
57
|
+
<Tabs
|
|
58
|
+
screenOptions={{
|
|
59
|
+
tabBarActiveTintColor: '#007AFF',
|
|
60
|
+
headerShown: true,
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
<Tabs.Screen
|
|
64
|
+
name="index"
|
|
65
|
+
options={{
|
|
66
|
+
title: 'Home',
|
|
67
|
+
tabBarIcon: ({ color, size }) => (
|
|
68
|
+
<Ionicons name="home" color={color} size={size} />
|
|
69
|
+
),
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
<Tabs.Screen
|
|
73
|
+
name="profile"
|
|
74
|
+
options={{
|
|
75
|
+
title: 'Profile',
|
|
76
|
+
tabBarIcon: ({ color, size }) => (
|
|
77
|
+
<Ionicons name="person" color={color} size={size} />
|
|
78
|
+
),
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
</Tabs>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Navigation
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { router, useLocalSearchParams, Link } from 'expo-router';
|
|
90
|
+
|
|
91
|
+
// Programmatic navigation
|
|
92
|
+
router.push('/details/123'); // Push to stack
|
|
93
|
+
router.replace('/home'); // Replace current
|
|
94
|
+
router.back(); // Go back
|
|
95
|
+
router.canGoBack(); // Check if can go back
|
|
96
|
+
|
|
97
|
+
// With params
|
|
98
|
+
router.push({
|
|
99
|
+
pathname: '/details/[id]',
|
|
100
|
+
params: { id: '123', title: 'Item' },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Link component
|
|
104
|
+
<Link href="/profile" asChild>
|
|
105
|
+
<Pressable>
|
|
106
|
+
<Text>Go to Profile</Text>
|
|
107
|
+
</Pressable>
|
|
108
|
+
</Link>
|
|
109
|
+
|
|
110
|
+
// Reading params
|
|
111
|
+
function DetailsScreen() {
|
|
112
|
+
const { id, title } = useLocalSearchParams<{ id: string; title?: string }>();
|
|
113
|
+
return <Text>Details for {id}</Text>;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Protected Routes
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// app/(auth)/_layout.tsx
|
|
121
|
+
import { Redirect, Stack } from 'expo-router';
|
|
122
|
+
import { useAuth } from '@/hooks/useAuth';
|
|
123
|
+
|
|
124
|
+
export default function AuthLayout() {
|
|
125
|
+
const { user, isLoading } = useAuth();
|
|
126
|
+
|
|
127
|
+
if (isLoading) {
|
|
128
|
+
return <LoadingScreen />;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (user) {
|
|
132
|
+
return <Redirect href="/(tabs)" />;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return <Stack screenOptions={{ headerShown: false }} />;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// app/(tabs)/_layout.tsx
|
|
139
|
+
export default function TabLayout() {
|
|
140
|
+
const { user, isLoading } = useAuth();
|
|
141
|
+
|
|
142
|
+
if (isLoading) {
|
|
143
|
+
return <LoadingScreen />;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!user) {
|
|
147
|
+
return <Redirect href="/(auth)/login" />;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return <Tabs>...</Tabs>;
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Deep Linking
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
// app.json
|
|
158
|
+
{
|
|
159
|
+
"expo": {
|
|
160
|
+
"scheme": "myapp",
|
|
161
|
+
"web": {
|
|
162
|
+
"bundler": "metro"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Handle: myapp://details/123
|
|
170
|
+
// app/details/[id].tsx handles automatically
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Quick Reference
|
|
174
|
+
|
|
175
|
+
| Component | Purpose |
|
|
176
|
+
|-----------|---------|
|
|
177
|
+
| `<Stack>` | Stack navigator |
|
|
178
|
+
| `<Tabs>` | Tab navigator |
|
|
179
|
+
| `<Drawer>` | Drawer navigator |
|
|
180
|
+
| `<Link>` | Declarative navigation |
|
|
181
|
+
|
|
182
|
+
| router method | Behavior |
|
|
183
|
+
|---------------|----------|
|
|
184
|
+
| `push()` | Add to stack |
|
|
185
|
+
| `replace()` | Replace current |
|
|
186
|
+
| `back()` | Go back |
|
|
187
|
+
| `dismissAll()` | Dismiss modals |
|