expo-tvos-search 1.3.0 → 1.3.1
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 +297 -162
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,29 +5,17 @@
|
|
|
5
5
|
[](https://github.com/keiver/expo-tvos-search/actions)
|
|
6
6
|
[](https://bundlephobia.com/package/expo-tvos-search)
|
|
7
7
|
|
|
8
|
-
A native tvOS search component for Expo and React Native using SwiftUI's `.searchable` modifier.
|
|
8
|
+
A native tvOS search component for Expo and React Native using SwiftUI's `.searchable` modifier. Provides the native tvOS search experience with automatic focus handling, remote control support, and flexible customization for media apps.
|
|
9
|
+
|
|
10
|
+
**Platform Support:**
|
|
11
|
+
- tvOS 15.0+
|
|
12
|
+
- Expo SDK 51+
|
|
13
|
+
- React Native tvOS 0.71+
|
|
9
14
|
|
|
10
15
|
<p align="center">
|
|
11
|
-
<img src="screenshots/
|
|
16
|
+
<img src="screenshots/demo-mini.png" width="80%" alt="Demo Mini screen for expo-tvos-search" style="border-radius: 16px;max-width: 100%;"/>
|
|
12
17
|
</p>
|
|
13
18
|
|
|
14
|
-
<table>
|
|
15
|
-
<tr>
|
|
16
|
-
<td align="center">
|
|
17
|
-
<img src="screenshots/default.png" width="280" alt="Search"/><br/>
|
|
18
|
-
<sub>Native Search</sub>
|
|
19
|
-
</td>
|
|
20
|
-
<td align="center">
|
|
21
|
-
<img src="screenshots/results.png" width="280" alt="Results"/><br/>
|
|
22
|
-
<sub>Results</sub>
|
|
23
|
-
</td>
|
|
24
|
-
<td align="center">
|
|
25
|
-
<img src="screenshots/no-results.png" width="280" alt="No Results"/><br/>
|
|
26
|
-
<sub>Empty State</sub>
|
|
27
|
-
</td>
|
|
28
|
-
</tr>
|
|
29
|
-
</table>
|
|
30
|
-
|
|
31
19
|
## Installation
|
|
32
20
|
|
|
33
21
|
```bash
|
|
@@ -42,6 +30,31 @@ npx expo install github:keiver/expo-tvos-search
|
|
|
42
30
|
|
|
43
31
|
Then follow the **tvOS prerequisites** below and rebuild your native project.
|
|
44
32
|
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### Try the Demo App
|
|
36
|
+
|
|
37
|
+
**[expo-tvos-search-demo](https://github.com/keiver/expo-tvos-search-demo)** - Comprehensive showcase with 7 tabs demonstrating all library features:
|
|
38
|
+
|
|
39
|
+
- **Default** - 4-column grid with custom colors
|
|
40
|
+
- **Portrait** - Netflix-style tall cards (280×420)
|
|
41
|
+
- **Landscape** - Wide 16:9 cards (500×280)
|
|
42
|
+
- **Mini** - Compact 5-column layout (240×360)
|
|
43
|
+
- **External Title** - Titles displayed below cards
|
|
44
|
+
- **Minimal** - Bare minimum setup (5 props)
|
|
45
|
+
- **Help** - Feature overview and usage guide
|
|
46
|
+
|
|
47
|
+
Clone and run:
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/keiver/expo-tvos-search-demo.git
|
|
50
|
+
cd expo-tvos-search-demo
|
|
51
|
+
npm install
|
|
52
|
+
npm run prebuild
|
|
53
|
+
npm run tvos
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The demo uses a planet search theme with 8 planets (Mercury to Neptune) and demonstrates all library features with real working code.
|
|
57
|
+
|
|
45
58
|
## Prerequisites for tvOS Builds (Expo)
|
|
46
59
|
|
|
47
60
|
Your project must be configured for React Native tvOS to build and run this module.
|
|
@@ -90,37 +103,46 @@ Then run:
|
|
|
90
103
|
npx expo run:ios
|
|
91
104
|
```
|
|
92
105
|
|
|
93
|
-
### 4. Common gotchas
|
|
94
|
-
|
|
95
|
-
- **Prebuild must be re-run** if you add/remove tvOS dependencies or change the tvOS plugin configuration.
|
|
96
|
-
- **If you see App Transport Security errors** for images, ensure your `imageUrl` uses `https://` (recommended) or add the appropriate ATS exceptions.
|
|
97
|
-
- **If the tvOS keyboard/search UI doesn't appear**, confirm you're actually running a tvOS target/simulator, not an iOS target.
|
|
98
106
|
|
|
99
107
|
## Usage
|
|
100
108
|
|
|
109
|
+
### Minimal Example
|
|
110
|
+
|
|
111
|
+
For the absolute minimum setup, see the [Minimal tab](https://github.com/keiver/expo-tvos-search-demo/blob/main/app/(tabs)/minimal.tsx) in the demo app.
|
|
112
|
+
|
|
113
|
+
<p align="center">
|
|
114
|
+
<img src="screenshots/demo-default.png" width="80%" alt="Minimal demo screen for expo-tvos-search" style="border-radius: 16px;max-width: 100%;"/><br/>
|
|
115
|
+
</p>
|
|
116
|
+
|
|
117
|
+
### Complete Example
|
|
118
|
+
|
|
119
|
+
This example from the demo's [Portrait tab](https://github.com/keiver/expo-tvos-search-demo/blob/main/app/(tabs)/portrait.tsx) shows a complete implementation with best practices:
|
|
120
|
+
|
|
101
121
|
```tsx
|
|
102
|
-
import
|
|
103
|
-
import { Alert } from
|
|
122
|
+
import { useState } from 'react';
|
|
123
|
+
import { Alert } from 'react-native';
|
|
124
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
125
|
+
import { LinearGradient } from 'expo-linear-gradient';
|
|
104
126
|
import {
|
|
105
127
|
TvosSearchView,
|
|
106
128
|
isNativeSearchAvailable,
|
|
107
129
|
type SearchResult,
|
|
108
|
-
} from
|
|
130
|
+
} from 'expo-tvos-search';
|
|
109
131
|
|
|
110
132
|
const PLANETS: SearchResult[] = [
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
{ id: "neptune", title: "Neptune", subtitle: "Windiest planet", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/6/63/Neptune_-_Voyager_2_%2829347980845%29_flatten_crop.jpg/400px-Neptune_-_Voyager_2_%2829347980845%29_flatten_crop.jpg" },
|
|
133
|
+
{
|
|
134
|
+
id: 'earth',
|
|
135
|
+
title: 'Earth - The Blue Marble of Life',
|
|
136
|
+
subtitle: 'Our home planet, the only known world to harbor life',
|
|
137
|
+
imageUrl: require('./assets/planets/earth.webp'),
|
|
138
|
+
},
|
|
139
|
+
// ... more planets
|
|
119
140
|
];
|
|
120
141
|
|
|
121
|
-
export function SearchScreen() {
|
|
142
|
+
export default function SearchScreen() {
|
|
122
143
|
const [results, setResults] = useState<SearchResult[]>([]);
|
|
123
144
|
const [isLoading, setIsLoading] = useState(false);
|
|
145
|
+
const insets = useSafeAreaInsets();
|
|
124
146
|
|
|
125
147
|
const handleSearch = (event: { nativeEvent: { query: string } }) => {
|
|
126
148
|
const { query } = event.nativeEvent;
|
|
@@ -132,11 +154,12 @@ export function SearchScreen() {
|
|
|
132
154
|
|
|
133
155
|
setIsLoading(true);
|
|
134
156
|
|
|
135
|
-
//
|
|
157
|
+
// Debounce search (300ms)
|
|
136
158
|
setTimeout(() => {
|
|
137
|
-
const filtered = PLANETS.filter(
|
|
138
|
-
planet
|
|
139
|
-
|
|
159
|
+
const filtered = PLANETS.filter(
|
|
160
|
+
planet =>
|
|
161
|
+
planet.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
162
|
+
planet.subtitle?.toLowerCase().includes(query.toLowerCase())
|
|
140
163
|
);
|
|
141
164
|
setResults(filtered);
|
|
142
165
|
setIsLoading(false);
|
|
@@ -144,157 +167,239 @@ export function SearchScreen() {
|
|
|
144
167
|
};
|
|
145
168
|
|
|
146
169
|
const handleSelect = (event: { nativeEvent: { id: string } }) => {
|
|
147
|
-
const planet = PLANETS.find(
|
|
170
|
+
const planet = PLANETS.find(p => p.id === event.nativeEvent.id);
|
|
148
171
|
if (planet) {
|
|
149
172
|
Alert.alert(planet.title, planet.subtitle);
|
|
150
173
|
}
|
|
151
174
|
};
|
|
152
175
|
|
|
153
176
|
if (!isNativeSearchAvailable()) {
|
|
154
|
-
return
|
|
177
|
+
return null; // Or show web fallback
|
|
155
178
|
}
|
|
156
179
|
|
|
157
180
|
return (
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
columns={5}
|
|
161
|
-
placeholder="Search planets..."
|
|
162
|
-
isLoading={isLoading}
|
|
163
|
-
topInset={140}
|
|
164
|
-
onSearch={handleSearch}
|
|
165
|
-
onSelectItem={handleSelect}
|
|
181
|
+
<LinearGradient
|
|
182
|
+
colors={['#0f172a', '#1e293b', '#0f172a']}
|
|
166
183
|
style={{ flex: 1 }}
|
|
167
|
-
|
|
184
|
+
>
|
|
185
|
+
<TvosSearchView
|
|
186
|
+
results={results}
|
|
187
|
+
columns={4}
|
|
188
|
+
placeholder="Search planets..."
|
|
189
|
+
isLoading={isLoading}
|
|
190
|
+
topInset={insets.top + 80}
|
|
191
|
+
onSearch={handleSearch}
|
|
192
|
+
onSelectItem={handleSelect}
|
|
193
|
+
textColor="#E5E5E5"
|
|
194
|
+
accentColor="#E50914"
|
|
195
|
+
cardWidth={280}
|
|
196
|
+
cardHeight={420}
|
|
197
|
+
overlayTitleSize={18} // v1.3.0 - control title font size
|
|
198
|
+
style={{ flex: 1 }}
|
|
199
|
+
/>
|
|
200
|
+
</LinearGradient>
|
|
168
201
|
);
|
|
169
202
|
}
|
|
170
203
|
```
|
|
171
204
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
###
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
## Layout Styles
|
|
206
|
+
|
|
207
|
+
Explore all 7 configurations in the [demo app](https://github.com/keiver/expo-tvos-search-demo).
|
|
208
|
+
|
|
209
|
+
### Portrait Cards
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
<TvosSearchView
|
|
213
|
+
columns={4}
|
|
214
|
+
cardWidth={280}
|
|
215
|
+
cardHeight={420}
|
|
216
|
+
overlayTitleSize={18}
|
|
217
|
+
// ... other props
|
|
218
|
+
/>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Landscape Cards
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
<TvosSearchView
|
|
225
|
+
columns={3}
|
|
226
|
+
cardWidth={500}
|
|
227
|
+
cardHeight={280}
|
|
228
|
+
// ... other props
|
|
229
|
+
/>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Mini Grid
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
<TvosSearchView
|
|
236
|
+
columns={5}
|
|
237
|
+
cardWidth={240}
|
|
238
|
+
cardHeight={360}
|
|
239
|
+
cardMargin={60} // v1.3.0 - extra spacing
|
|
240
|
+
// ... other props
|
|
241
|
+
/>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### External Titles
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
<TvosSearchView
|
|
248
|
+
showTitle={true}
|
|
249
|
+
showSubtitle={true}
|
|
250
|
+
showTitleOverlay={false}
|
|
251
|
+
// ... other props
|
|
252
|
+
/>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Error Handling
|
|
202
256
|
|
|
203
257
|
```tsx
|
|
204
258
|
<TvosSearchView
|
|
205
|
-
results={results}
|
|
206
|
-
onSearch={handleSearch}
|
|
207
|
-
onSelectItem={handleSelect}
|
|
208
259
|
onError={(e) => {
|
|
209
260
|
const { category, message, context } = e.nativeEvent;
|
|
210
|
-
// Log to your error monitoring service
|
|
211
261
|
console.error(`[Search Error] ${category}: ${message}`, context);
|
|
212
|
-
// Examples: 'module_unavailable', 'validation_failed', 'image_load_failed', 'unknown'
|
|
213
262
|
}}
|
|
214
263
|
onValidationWarning={(e) => {
|
|
215
264
|
const { type, message, context } = e.nativeEvent;
|
|
216
|
-
// Log non-fatal issues for monitoring
|
|
217
265
|
console.warn(`[Validation] ${type}: ${message}`, context);
|
|
218
|
-
// Examples: 'field_truncated', 'value_clamped', 'url_invalid', 'validation_failed'
|
|
219
266
|
}}
|
|
267
|
+
// ... other props
|
|
220
268
|
/>
|
|
221
269
|
```
|
|
222
270
|
|
|
223
|
-
These callbacks help you:
|
|
224
|
-
- Monitor data quality issues (truncated fields, invalid URLs)
|
|
225
|
-
- Track when props are clamped to safe ranges
|
|
226
|
-
- Detect when results exceed the 500-item limit
|
|
227
|
-
- Log errors for debugging in production
|
|
228
|
-
|
|
229
271
|
### Customizing Colors and Card Dimensions
|
|
230
272
|
|
|
231
|
-
You can customize the appearance of the search interface with color and dimension props:
|
|
232
|
-
|
|
233
273
|
```tsx
|
|
234
274
|
<TvosSearchView
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
accentColor="#E50914" // Red focused borders (House Flix style 😂)
|
|
241
|
-
// Custom card dimensions
|
|
242
|
-
cardWidth={420} // Landscape cards
|
|
243
|
-
cardHeight={240} // 16:9 aspect ratio
|
|
244
|
-
style={{ flex: 1 }}
|
|
275
|
+
textColor="#E5E5E5"
|
|
276
|
+
accentColor="#E50914"
|
|
277
|
+
cardWidth={420}
|
|
278
|
+
cardHeight={240}
|
|
279
|
+
// ... other props
|
|
245
280
|
/>
|
|
246
281
|
```
|
|
247
282
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
283
|
+
### Title Overlay Customization (v1.3.0+)
|
|
284
|
+
|
|
285
|
+
```tsx
|
|
286
|
+
<TvosSearchView
|
|
287
|
+
overlayTitleSize={22}
|
|
288
|
+
enableMarquee={true}
|
|
289
|
+
marqueeDelay={1.5}
|
|
290
|
+
// ... other props
|
|
291
|
+
/>
|
|
292
|
+
```
|
|
251
293
|
|
|
252
|
-
|
|
253
|
-
- Default: 280x420 (portrait, 2:3 aspect ratio)
|
|
254
|
-
- Landscape example: 420x240 (16:9)
|
|
255
|
-
- Square example: 300x300 (1:1)
|
|
294
|
+
### Layout Spacing (v1.3.0+)
|
|
256
295
|
|
|
257
|
-
|
|
296
|
+
```tsx
|
|
297
|
+
<TvosSearchView
|
|
298
|
+
cardMargin={60}
|
|
299
|
+
cardPadding={25}
|
|
300
|
+
// ... other props
|
|
301
|
+
/>
|
|
302
|
+
```
|
|
258
303
|
|
|
259
|
-
|
|
304
|
+
### Image Display Mode
|
|
260
305
|
|
|
261
306
|
```tsx
|
|
262
307
|
<TvosSearchView
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
onSelectItem={handleSelect}
|
|
266
|
-
// Image display mode
|
|
267
|
-
imageContentMode="fit" // 'fill' (default, crop), 'fit'/'contain' (letterbox)
|
|
268
|
-
// Card spacing
|
|
269
|
-
cardMargin={60} // Space between cards (default: 40)
|
|
270
|
-
cardPadding={20} // Padding inside overlay (default: 16)
|
|
271
|
-
style={{ flex: 1 }}
|
|
308
|
+
imageContentMode="fit" // 'fill' (crop), 'fit'/'contain' (letterbox)
|
|
309
|
+
// ... other props
|
|
272
310
|
/>
|
|
273
311
|
```
|
|
274
312
|
|
|
275
|
-
|
|
276
|
-
- `imageContentMode` - How images fill cards:
|
|
277
|
-
- `'fill'` (default) - Crops image to fill entire card
|
|
278
|
-
- `'fit'` or `'contain'` - Shows entire image, may add letterboxing
|
|
279
|
-
- `cardMargin` - Controls spacing between cards in the grid (both horizontal and vertical)
|
|
280
|
-
- `cardPadding` - Controls padding inside the card's title overlay for better text spacing
|
|
313
|
+
## TypeScript Support
|
|
281
314
|
|
|
282
|
-
|
|
283
|
-
- **Portrait posters** (movie/TV): `imageContentMode="fill"` + `cardMargin={40}`
|
|
284
|
-
- **Landscape thumbnails** (episodes): `imageContentMode="fit"` + `cardMargin={60}`
|
|
285
|
-
- **Compact grid** (music): `cardMargin={20}` + `cardPadding={12}`
|
|
286
|
-
- **Spacious layout** (photos): `cardMargin={60}` + `cardPadding={20}`
|
|
315
|
+
The library provides comprehensive type definitions for all events and props.
|
|
287
316
|
|
|
288
|
-
|
|
317
|
+
### Event Types
|
|
289
318
|
|
|
290
|
-
|
|
319
|
+
```typescript
|
|
320
|
+
import type {
|
|
321
|
+
SearchEvent,
|
|
322
|
+
SelectItemEvent,
|
|
323
|
+
SearchViewErrorEvent,
|
|
324
|
+
ValidationWarningEvent,
|
|
325
|
+
SearchResult,
|
|
326
|
+
} from 'expo-tvos-search';
|
|
327
|
+
|
|
328
|
+
// Search event - fired on text change
|
|
329
|
+
interface SearchEvent {
|
|
330
|
+
nativeEvent: {
|
|
331
|
+
query: string;
|
|
332
|
+
};
|
|
333
|
+
}
|
|
291
334
|
|
|
292
|
-
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
335
|
+
// Selection event - fired when result is selected
|
|
336
|
+
interface SelectItemEvent {
|
|
337
|
+
nativeEvent: {
|
|
338
|
+
id: string;
|
|
339
|
+
};
|
|
340
|
+
}
|
|
296
341
|
|
|
297
|
-
|
|
342
|
+
// Error event - fatal errors (v1.2.0+)
|
|
343
|
+
interface SearchViewErrorEvent {
|
|
344
|
+
nativeEvent: {
|
|
345
|
+
category: 'module_unavailable' | 'validation_failed' | 'image_load_failed' | 'unknown';
|
|
346
|
+
message: string;
|
|
347
|
+
context?: string;
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Validation warning - non-fatal issues (v1.2.0+)
|
|
352
|
+
interface ValidationWarningEvent {
|
|
353
|
+
nativeEvent: {
|
|
354
|
+
type: 'field_truncated' | 'value_clamped' | 'url_invalid' | 'validation_failed';
|
|
355
|
+
message: string;
|
|
356
|
+
context?: string;
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Search result shape
|
|
361
|
+
interface SearchResult {
|
|
362
|
+
id: string; // Required, max 500 chars
|
|
363
|
+
title: string; // Required, max 500 chars
|
|
364
|
+
subtitle?: string; // Optional, max 500 chars
|
|
365
|
+
imageUrl?: string; // Optional, HTTPS recommended
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Typed Usage
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
const handleSearch = (event: SearchEvent) => {
|
|
373
|
+
const query = event.nativeEvent.query;
|
|
374
|
+
// TypeScript knows query is a string
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const handleSelect = (event: SelectItemEvent) => {
|
|
378
|
+
const id = event.nativeEvent.id;
|
|
379
|
+
// TypeScript knows id is a string
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const handleError = (event: SearchViewErrorEvent) => {
|
|
383
|
+
const { category, message, context } = event.nativeEvent;
|
|
384
|
+
// Full autocomplete for category values
|
|
385
|
+
if (category === 'image_load_failed') {
|
|
386
|
+
logger.warn(`Image failed to load: ${message}`, { context });
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Demo Apps & Examples
|
|
392
|
+
|
|
393
|
+
### Official Demo App
|
|
394
|
+
|
|
395
|
+
**[expo-tvos-search-demo](https://github.com/keiver/expo-tvos-search-demo)** - Complete working examples with 7 different layout styles. Browse the [source code](https://github.com/keiver/expo-tvos-search-demo/tree/main/app/(tabs)) for each configuration.
|
|
396
|
+
|
|
397
|
+
### Apps Using This Library
|
|
398
|
+
|
|
399
|
+
**[Tomo TV](https://github.com/keiver/tomotv)** - Full-featured tvOS Jellyfin client
|
|
400
|
+
- Real-world integration with media library API
|
|
401
|
+
- Advanced search with live server calls
|
|
402
|
+
- Complete authentication and navigation flow
|
|
298
403
|
|
|
299
404
|
## See it in action:
|
|
300
405
|
|
|
@@ -304,35 +409,72 @@ Check out Tomo TV to see `expo-tvos-search` in action and reference its implemen
|
|
|
304
409
|
|
|
305
410
|
## Props
|
|
306
411
|
|
|
412
|
+
### Core Props
|
|
413
|
+
|
|
307
414
|
| Prop | Type | Default | Description |
|
|
308
415
|
|------|------|---------|-------------|
|
|
309
416
|
| `results` | `SearchResult[]` | `[]` | Array of search results |
|
|
310
417
|
| `columns` | `number` | `5` | Number of columns in the grid |
|
|
311
418
|
| `placeholder` | `string` | `"Search movies and videos..."` | Search field placeholder |
|
|
312
419
|
| `isLoading` | `boolean` | `false` | Shows loading indicator |
|
|
420
|
+
|
|
421
|
+
### Card Dimensions & Spacing
|
|
422
|
+
|
|
423
|
+
| Prop | Type | Default | Description |
|
|
424
|
+
|------|------|---------|-------------|
|
|
425
|
+
| `cardWidth` | `number` | `280` | Width of each result card in points |
|
|
426
|
+
| `cardHeight` | `number` | `420` | Height of each result card in points |
|
|
427
|
+
| `cardMargin` | `number` | `40` | **(v1.3.0+)** Spacing between cards in the grid (horizontal and vertical) |
|
|
428
|
+
| `cardPadding` | `number` | `16` | **(v1.3.0+)** Padding inside the card for overlay content (title/subtitle) |
|
|
429
|
+
| `topInset` | `number` | `0` | Top padding (for tab bar clearance) |
|
|
430
|
+
|
|
431
|
+
### Display Options
|
|
432
|
+
|
|
433
|
+
| Prop | Type | Default | Description |
|
|
434
|
+
|------|------|---------|-------------|
|
|
313
435
|
| `showTitle` | `boolean` | `false` | Show title below each result |
|
|
314
436
|
| `showSubtitle` | `boolean` | `false` | Show subtitle below title |
|
|
315
|
-
| `showFocusBorder` | `boolean` | `false` | Show border on focused item |
|
|
316
|
-
| `topInset` | `number` | `0` | Top padding (for tab bar clearance) |
|
|
317
437
|
| `showTitleOverlay` | `boolean` | `true` | Show title overlay with gradient at bottom of card |
|
|
438
|
+
| `showFocusBorder` | `boolean` | `false` | Show border on focused item |
|
|
439
|
+
| `imageContentMode` | `'fill' \| 'fit' \| 'contain'` | `'fill'` | How images fill the card: `fill` (crop to fill), `fit`/`contain` (letterbox) |
|
|
440
|
+
|
|
441
|
+
### Styling & Colors
|
|
442
|
+
|
|
443
|
+
| Prop | Type | Default | Description |
|
|
444
|
+
|------|------|---------|-------------|
|
|
445
|
+
| `textColor` | `string` | system default | Color for text and UI elements (hex format, e.g., "#FFFFFF") |
|
|
446
|
+
| `accentColor` | `string` | `"#FFC312"` | Accent color for focused elements (hex format, e.g., "#FFC312") |
|
|
447
|
+
| `overlayTitleSize` | `number` | `20` | **(v1.3.0+)** Font size for title text in the blur overlay (when showTitleOverlay is true) |
|
|
448
|
+
|
|
449
|
+
### Animation
|
|
450
|
+
|
|
451
|
+
| Prop | Type | Default | Description |
|
|
452
|
+
|------|------|---------|-------------|
|
|
318
453
|
| `enableMarquee` | `boolean` | `true` | Enable marquee scrolling for long titles |
|
|
319
454
|
| `marqueeDelay` | `number` | `1.5` | Delay in seconds before marquee starts |
|
|
455
|
+
|
|
456
|
+
### Text Customization
|
|
457
|
+
|
|
458
|
+
| Prop | Type | Default | Description |
|
|
459
|
+
|------|------|---------|-------------|
|
|
320
460
|
| `emptyStateText` | `string` | `"Search for movies and videos"` | Text shown when search field is empty |
|
|
321
461
|
| `searchingText` | `string` | `"Searching..."` | Text shown during search |
|
|
322
462
|
| `noResultsText` | `string` | `"No results found"` | Text shown when no results found |
|
|
323
463
|
| `noResultsHintText` | `string` | `"Try a different search term"` | Hint text below no results message |
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
|
328
|
-
|
|
329
|
-
| `cardMargin` | `number` | `40` | Spacing between cards in the grid (horizontal and vertical) |
|
|
330
|
-
| `cardPadding` | `number` | `16` | Padding inside the card for overlay content (title/subtitle) |
|
|
331
|
-
| `overlayTitleSize` | `number` | `20` | Font size for title text in the blur overlay (when showTitleOverlay is true) |
|
|
464
|
+
|
|
465
|
+
### Event Handlers
|
|
466
|
+
|
|
467
|
+
| Prop | Type | Default | Description |
|
|
468
|
+
|------|------|---------|-------------|
|
|
332
469
|
| `onSearch` | `function` | required | Called when search text changes |
|
|
333
470
|
| `onSelectItem` | `function` | required | Called when result is selected |
|
|
334
|
-
| `onError` | `function` | optional | Called when errors occur (image loading failures, validation errors) |
|
|
335
|
-
| `onValidationWarning` | `function` | optional | Called for non-fatal warnings (truncated fields, clamped values, invalid URLs) |
|
|
471
|
+
| `onError` | `function` | optional | **(v1.2.0+)** Called when errors occur (image loading failures, validation errors) |
|
|
472
|
+
| `onValidationWarning` | `function` | optional | **(v1.2.0+)** Called for non-fatal warnings (truncated fields, clamped values, invalid URLs) |
|
|
473
|
+
|
|
474
|
+
### Other
|
|
475
|
+
|
|
476
|
+
| Prop | Type | Default | Description |
|
|
477
|
+
|------|------|---------|-------------|
|
|
336
478
|
| `style` | `ViewStyle` | optional | Style object for the view container |
|
|
337
479
|
|
|
338
480
|
## SearchResult Type
|
|
@@ -420,13 +562,6 @@ function SearchScreen() {
|
|
|
420
562
|
}
|
|
421
563
|
```
|
|
422
564
|
|
|
423
|
-
## Requirements
|
|
424
|
-
|
|
425
|
-
- Node.js 18+
|
|
426
|
-
- Expo SDK 51+
|
|
427
|
-
- tvOS 15+
|
|
428
|
-
- Project configured for tvOS (`react-native-tvos` + `@react-native-tvos/config-tv`)
|
|
429
|
-
|
|
430
565
|
## Troubleshooting
|
|
431
566
|
|
|
432
567
|
### Native module not found
|
|
@@ -489,7 +624,7 @@ We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guid
|
|
|
489
624
|
|
|
490
625
|
### Adding New Props
|
|
491
626
|
|
|
492
|
-
If you're adding new props to the library, follow the comprehensive checklist in [
|
|
627
|
+
If you're adding new props to the library, follow the comprehensive checklist in [CLAUDE-adding-new-props.md](./CLAUDE-adding-new-props.md). This memory bank provides a 9-step guide ensuring props are properly wired from TypeScript through to Swift rendering.
|
|
493
628
|
|
|
494
629
|
## License
|
|
495
630
|
|