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.
Files changed (2) hide show
  1. package/README.md +297 -162
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -5,29 +5,17 @@
5
5
  [![Test Status](https://github.com/keiver/expo-tvos-search/workflows/Test%20PR/badge.svg)](https://github.com/keiver/expo-tvos-search/actions)
6
6
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/expo-tvos-search)](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. Handles focus, keyboard navigation, and accessibility out of the box.
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/results.png" width="700" alt="TomoTV Search Results"/>
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 React, { useState } from "react";
103
- import { Alert } from "react-native";
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 "expo-tvos-search";
130
+ } from 'expo-tvos-search';
109
131
 
110
132
  const PLANETS: SearchResult[] = [
111
- { id: "mercury", title: "Mercury", subtitle: "Smallest planet", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Mercury_in_true_color.jpg/400px-Mercury_in_true_color.jpg" },
112
- { id: "venus", title: "Venus", subtitle: "Hottest planet", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e5/Venus-real_color.jpg/400px-Venus-real_color.jpg" },
113
- { id: "earth", title: "Earth", subtitle: "Our home", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/The_Earth_seen_from_Apollo_17.jpg/400px-The_Earth_seen_from_Apollo_17.jpg" },
114
- { id: "mars", title: "Mars", subtitle: "The red planet", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/OSIRIS_Mars_true_color.jpg/400px-OSIRIS_Mars_true_color.jpg" },
115
- { id: "jupiter", title: "Jupiter", subtitle: "Largest planet", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Jupiter_New_Horizons.jpg/400px-Jupiter_New_Horizons.jpg" },
116
- { id: "saturn", title: "Saturn", subtitle: "Ringed giant", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/Saturn_during_Equinox.jpg/400px-Saturn_during_Equinox.jpg" },
117
- { id: "uranus", title: "Uranus", subtitle: "Ice giant", imageUrl: "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3d/Uranus2.jpg/400px-Uranus2.jpg" },
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
- // Simulate async search
157
+ // Debounce search (300ms)
136
158
  setTimeout(() => {
137
- const filtered = PLANETS.filter((planet) =>
138
- planet.title.toLowerCase().includes(query.toLowerCase()) ||
139
- planet.subtitle?.toLowerCase().includes(query.toLowerCase())
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((p) => p.id === event.nativeEvent.id);
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 <YourFallbackSearch />;
177
+ return null; // Or show web fallback
155
178
  }
156
179
 
157
180
  return (
158
- <TvosSearchView
159
- results={results}
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
- > **⚠️ IMPORTANT**: Always debounce the `onSearch` callback to prevent excessive API calls or expensive operations on every keystroke. In production apps, use a debounce library like lodash or implement your own debounce function:
173
- >
174
- > ```tsx
175
- > import { useMemo } from 'react';
176
- > import { debounce } from 'lodash'; // or your preferred debounce implementation
177
- >
178
- > function SearchScreen() {
179
- > const [results, setResults] = useState([]);
180
- >
181
- > const debouncedSearch = useMemo(
182
- > () => debounce((query: string) => {
183
- > // Your actual search logic (API call, etc.)
184
- > fetchSearchResults(query).then(setResults);
185
- > }, 300), // 300ms delay
186
- > []
187
- > );
188
- >
189
- > return (
190
- > <TvosSearchView
191
- > results={results}
192
- > onSearch={(e) => debouncedSearch(e.nativeEvent.query)}
193
- > onSelectItem={handleSelect}
194
- > />
195
- > );
196
- > }
197
- > ```
198
-
199
- ### Error Handling and Monitoring
200
-
201
- The library provides error and validation warning callbacks for production monitoring:
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
- results={results}
236
- onSearch={handleSearch}
237
- onSelectItem={handleSelect}
238
- // Custom colors
239
- textColor="#E5E5E5" // Light gray text on dark background
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
- **Color props:**
249
- - `textColor` - Affects subtitle text, empty state text, and placeholder icons
250
- - `accentColor` - Affects focused card borders and highlights
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
- **Dimension props:**
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
- ### Layout and Spacing Customization
296
+ ```tsx
297
+ <TvosSearchView
298
+ cardMargin={60}
299
+ cardPadding={25}
300
+ // ... other props
301
+ />
302
+ ```
258
303
 
259
- Control image display, card spacing, and overlay padding for different layouts:
304
+ ### Image Display Mode
260
305
 
261
306
  ```tsx
262
307
  <TvosSearchView
263
- results={results}
264
- onSearch={handleSearch}
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
- **Layout props:**
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
- **Common use cases:**
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
- ## Example App
317
+ ### Event Types
289
318
 
290
- **[Tomo TV](https://github.com/keiver/tomotv)** is a tvOS application that uses `expo-tvos-search` in a real-world Jellyfin client. It demonstrates the search component integrated with a complete media browsing experience, including:
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
- - Search interaction with tvOS remote
293
- - Focus navigation through results
294
- - Integration with a live media library
295
- - Complete setup instructions and screenshots
335
+ // Selection event - fired when result is selected
336
+ interface SelectItemEvent {
337
+ nativeEvent: {
338
+ id: string;
339
+ };
340
+ }
296
341
 
297
- Check out Tomo TV to see `expo-tvos-search` in action and reference its implementation for your own projects.
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
- | `textColor` | `string` | system default | Color for text and UI elements (hex format, e.g., "#FFFFFF") |
325
- | `accentColor` | `string` | `"#FFC312"` | Accent color for focused elements (hex format, e.g., "#FFC312") |
326
- | `cardWidth` | `number` | `280` | Width of each result card in points |
327
- | `cardHeight` | `number` | `420` | Height of each result card in points |
328
- | `imageContentMode` | `'fill' \| 'fit' \| 'contain'` | `'fill'` | How images fill the card: `fill` (crop to fill), `fit`/`contain` (letterbox) |
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 [.claude/CLAUDE-adding-new-props.md](.claude/CLAUDE-adding-new-props.md). This memory bank provides a 9-step guide ensuring props are properly wired from TypeScript through to Swift rendering.
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-tvos-search",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Native tvOS search view using SwiftUI .searchable modifier for Expo/React Native",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",