expo-tvos-search 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.5.0] - 2026-02-01
9
+
10
+ ### Added
11
+ - `colorScheme` prop — override the system color scheme for the search view (`'dark'`, `'light'`, or `'system'`)
12
+ - Fixes illegible search bar text when app has a dark background and system is in light mode
13
+ - Maps to `UIHostingController.overrideUserInterfaceStyle` on the native side
14
+ - Default `"system"` preserves existing behavior (no breaking change)
15
+
16
+ ### Changed
17
+ - Search bar interactive controls (cursor, highlights) now tint to match `accentColor`
18
+
8
19
  ## [1.3.2] - 2026-01-25
9
20
 
10
21
  ### Added
package/README.md CHANGED
@@ -1,18 +1,57 @@
1
- # expo-tvos-search
1
+ # expo-tvos-search — Native tvOS Search for React Native tvOS (Expo)
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/expo-tvos-search.svg)](https://www.npmjs.com/package/expo-tvos-search)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
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
 
7
- 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.
8
-
9
- **Platform Support:**
10
- - tvOS 15.0+
11
- - Expo SDK 51+
12
- - React Native tvOS 0.71+
7
+ A native Apple TV search component built with SwiftUI's `.searchable` modifier. Drop it into your Expo tvOS app and get the system search experience keyboard, Siri Remote, focus handling out of the box.
8
+
9
+ ## Features
10
+
11
+ - **Native SwiftUI** — uses `.searchable` for the real tvOS search experience, not a web imitation
12
+ - **Siri Remote support** — full keyboard navigation, swipe, tap, and long-press handling on real hardware
13
+ - **Configurable grid** — portrait, landscape, square, or mini cards with adjustable columns, spacing, and padding
14
+ - **Marquee titles** — long titles auto-scroll on focus with configurable delay
15
+ - **Image caching** — async image loading with NSCache-backed caching
16
+ - **Title overlay** — gradient overlay with blur effect on card images, toggleable
17
+ - **External titles** — show title and subtitle below cards instead of (or alongside) the overlay
18
+ - **Customizable colors** — text color, accent/focus color, all via hex strings
19
+ - **Color scheme override** — force dark or light appearance regardless of system setting
20
+ - **Controlled search text** — set search field text programmatically for deep links, state restore, or "search for similar" flows
21
+ - **Error & validation callbacks** — structured error events and non-fatal validation warnings
22
+ - **Focus callbacks** — `onSearchFieldFocused` / `onSearchFieldBlurred` for gesture handler coordination
23
+ - **Platform-safe** — renders `null` on non-tvOS platforms; use `isNativeSearchAvailable()` to gate rendering
24
+
25
+ ## Table of Contents
26
+
27
+ - [Installation](#installation)
28
+ - [Prerequisites](#prerequisites-for-tvos-builds)
29
+ - [Quick Start](#quick-start)
30
+ - [Usage Examples](#usage-examples)
31
+ - [Portrait Cards](#portrait-cards)
32
+ - [Landscape Cards](#landscape-cards)
33
+ - [Mini Grid](#mini-grid)
34
+ - [External Titles](#external-titles)
35
+ - [Title Overlay Customization](#title-overlay-customization)
36
+ - [Layout Spacing](#layout-spacing)
37
+ - [Image Display Mode](#image-display-mode)
38
+ - [Color Scheme Override](#color-scheme-override)
39
+ - [Colors and Dimensions](#colors-and-dimensions)
40
+ - [Error Handling](#error-handling)
41
+ - [Apple TV Hardware Keyboard](#apple-tv-hardware-keyboard-support)
42
+ - [API Reference](#api-reference)
43
+ - [Props](#props)
44
+ - [SearchResult](#searchresult)
45
+ - [Event Types](#event-types)
46
+ - [isNativeSearchAvailable()](#isnativesearchavailable)
47
+ - [Result Validation](#result-validation)
48
+ - [Demo App](#demo-app)
49
+ - [Testing](#testing)
50
+ - [Contributing](#contributing)
51
+ - [License](#license)
13
52
 
14
53
  <p align="center">
15
- <img src="screenshots/default.png" alt="expo-tvos-search fullscreen native tvOS search component" style="border-radius: 16px;max-width: 100%;"/>
54
+ <img src="screenshots/demo-05.webp" alt="Native tvOS search with portrait card grid showing planet results with gold accent and marquee titles" width="100%"/>
16
55
  </p>
17
56
 
18
57
  ## Installation
@@ -29,7 +68,9 @@ npx expo install github:keiver/expo-tvos-search
29
68
 
30
69
  ## Prerequisites for tvOS Builds
31
70
 
32
- Your project must be configured for React Native tvOS to use this module.
71
+ Your project must be configured for React Native tvOS.
72
+
73
+ **Platform requirements:** tvOS 15.0+, Expo SDK 51+, React Native tvOS 0.71+
33
74
 
34
75
  ### 1. Install react-native-tvos
35
76
 
@@ -53,167 +94,50 @@ Then add the plugin in `app.json` / `app.config.js`:
53
94
  }
54
95
  ```
55
96
 
56
- ## Usage
57
-
58
- 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:
97
+ ## Quick Start
59
98
 
60
99
  ```tsx
61
- import { useState } from 'react';
62
- import { Alert } from 'react-native';
63
- import { useSafeAreaInsets } from 'react-native-safe-area-context';
64
- import { LinearGradient } from 'expo-linear-gradient';
65
- import {
66
- TvosSearchView,
67
- isNativeSearchAvailable,
68
- type SearchResult,
69
- } from 'expo-tvos-search';
70
-
71
- const PLANETS: SearchResult[] = [
100
+ import { TvosSearchView, type SearchResult } from 'expo-tvos-search';
101
+
102
+ const results: SearchResult[] = [
72
103
  {
73
104
  id: 'earth',
74
- title: 'Earth - The Blue Marble of Life',
75
- subtitle: 'Our home planet, the only known world to harbor life',
76
- imageUrl: require('./assets/planets/earth.webp'),
105
+ title: 'Earth',
106
+ subtitle: 'The Blue Marble',
107
+ imageUrl: 'https://example.com/earth.jpg',
108
+ },
109
+ {
110
+ id: 'mars',
111
+ title: 'Mars',
112
+ subtitle: 'The Red Planet',
113
+ imageUrl: 'https://example.com/mars.jpg',
77
114
  },
78
- // ... all planets
79
115
  ];
80
116
 
81
117
  export default function SearchScreen() {
82
- const [results, setResults] = useState<SearchResult[]>([]);
83
- const [isLoading, setIsLoading] = useState(false);
84
- const insets = useSafeAreaInsets();
85
-
86
- const handleSearch = (event: { nativeEvent: { query: string } }) => {
87
- const { query } = event.nativeEvent;
88
-
89
- if (!query.trim()) {
90
- setResults([]);
91
- return;
92
- }
93
-
94
- setIsLoading(true);
95
-
96
- // Debounce dummy search (300ms)
97
- setTimeout(() => {
98
- const filtered = PLANETS.filter(
99
- planet =>
100
- planet.title.toLowerCase().includes(query.toLowerCase()) ||
101
- planet.subtitle?.toLowerCase().includes(query.toLowerCase())
102
- );
103
- setResults(filtered);
104
- setIsLoading(false);
105
- }, 300);
106
- };
107
-
108
- const handleSelect = (event: { nativeEvent: { id: string } }) => {
109
- const planet = PLANETS.find(p => p.id === event.nativeEvent.id);
110
- if (planet) {
111
- Alert.alert(planet.title, planet.subtitle);
112
- }
113
- };
114
-
115
- if (!isNativeSearchAvailable()) {
116
- return null; // Or show web fallback
117
- }
118
-
119
118
  return (
120
- <LinearGradient
121
- colors={['#0f172a', '#1e293b', '#0f172a']}
119
+ <TvosSearchView
120
+ results={results}
121
+ columns={4}
122
+ placeholder="Search planets..."
123
+ isLoading={false}
124
+ topInset={80}
125
+ onSearch={(e) => console.log('Search:', e.nativeEvent.query)}
126
+ onSelectItem={(e) => console.log('Selected:', e.nativeEvent.id)}
127
+ textColor="#E5E5E5"
128
+ accentColor="#E50914"
129
+ cardWidth={280}
130
+ cardHeight={420}
131
+ overlayTitleSize={18}
122
132
  style={{ flex: 1 }}
123
- >
124
- <TvosSearchView
125
- results={results}
126
- columns={4}
127
- placeholder="Search planets..."
128
- isLoading={isLoading}
129
- topInset={insets.top + 80}
130
- onSearch={handleSearch}
131
- onSelectItem={handleSelect}
132
- textColor="#E5E5E5"
133
- accentColor="#E50914"
134
- cardWidth={280}
135
- cardHeight={420}
136
- overlayTitleSize={18}
137
- style={{ flex: 1 }}
138
- />
139
- </LinearGradient>
133
+ />
140
134
  );
141
135
  }
142
136
  ```
143
137
 
144
- <p align="center">
145
- <img src="screenshots/no-results.png" alt="No results for search screen using expo-tvos-search" style="border-radius: 16px;max-width: 100%;"/><br/>
146
- </p>
147
-
148
- ## Demo App and Common Configurations
149
-
150
- Explore all configurations in the [demo app](https://github.com/keiver/expo-tvos-search-demo).
151
-
152
- ### Portrait Cards
153
-
154
- ```tsx
155
- <TvosSearchView
156
- columns={4}
157
- cardWidth={280}
158
- cardHeight={420}
159
- overlayTitleSize={18}
160
- // ... other props
161
- />
162
- ```
163
-
164
- ### Landscape Cards
165
-
166
- ```tsx
167
- <TvosSearchView
168
- columns={3}
169
- cardWidth={500}
170
- cardHeight={280}
171
- // ... other props
172
- />
173
- ```
174
-
175
- ### Mini Grid
176
-
177
- ```tsx
178
- <TvosSearchView
179
- columns={5}
180
- cardWidth={240}
181
- cardHeight={360}
182
- cardMargin={60} // v1.3.0 - extra spacing
183
- // ... other props
184
- />
185
- ```
186
-
187
- ### External Titles
188
-
189
- ```tsx
190
- <TvosSearchView
191
- showTitle={true}
192
- showSubtitle={true}
193
- showTitleOverlay={false}
194
- // ... other props
195
- />
196
- ```
197
-
198
- ### Error Handling
199
-
200
- ```tsx
201
- <TvosSearchView
202
- onError={(e) => {
203
- const { category, message, context } = e.nativeEvent;
204
- console.error(`[Search Error] ${category}: ${message}`, context);
205
- }}
206
- onValidationWarning={(e) => {
207
- const { type, message, context } = e.nativeEvent;
208
- console.warn(`[Validation] ${type}: ${message}`, context);
209
- }}
210
- // ... other props
211
- />
212
- ```
213
-
214
- ### Apple TV Hardware Keyboard Support (v1.3.2+)
138
+ ### Apple TV Hardware Keyboard Support
215
139
 
216
- On real Apple TV hardware, React Native's `RCTTVRemoteHandler` installs gesture recognizers that consume Siri Remote presses before they reach SwiftUI's `.searchable` text field, which prevents keyboard input. When the search field gains focus, this component temporarily disables touch cancellation using the official `react-native-tvos` notification API, and also disables tap/long-press recognizers from parent views (to cover cases like `react-native-gesture-handler`). Swipe and pan recognizers stay active for keyboard navigation. Everything is restored when focus leaves the field. This only applies to physical devices -- the Simulator doesn't need it.
140
+ On real Apple TV hardware, React Native's `RCTTVRemoteHandler` installs gesture recognizers that consume Siri Remote presses before they reach SwiftUI's `.searchable` text field, which prevents keyboard input. When the search field gains focus, this component temporarily disables touch cancellation using the official `react-native-tvos` notification API, and also disables tap/long-press recognizers from parent views (to cover cases like `react-native-gesture-handler`). Swipe and pan recognizers stay active for keyboard navigation. Everything is restored when focus leaves the field. This only applies to physical devices the Simulator doesn't need it.
217
141
 
218
142
  If this interferes with gesture handling in your app, please [open an issue](https://github.com/keiver/expo-tvos-search/issues) so we can sort it out.
219
143
 
@@ -233,137 +157,140 @@ import { TVEventControl } from 'react-native';
233
157
  />
234
158
  ```
235
159
 
236
- ### Customizing Colors and Card Dimensions
237
-
238
- ```tsx
239
- <TvosSearchView
240
- textColor="#E5E5E5"
241
- accentColor="#E50914"
242
- cardWidth={420}
243
- cardHeight={240}
244
- // ... other props
245
- />
246
- ```
247
-
248
- ### Title Overlay Customization (v1.3.0+)
249
160
 
250
- ```tsx
251
- <TvosSearchView
252
- overlayTitleSize={22}
253
- enableMarquee={true}
254
- marqueeDelay={1.5}
255
- // ... other props
256
- />
257
- ```
161
+ ### Color Scheme Override
258
162
 
259
- ### Layout Spacing (v1.3.0+)
163
+ Apps with a fixed dark background can get illegible search bar text when the system is in light mode. Use `colorScheme` to force a specific appearance:
260
164
 
261
165
  ```tsx
262
166
  <TvosSearchView
263
- cardMargin={60}
264
- cardPadding={25}
167
+ colorScheme="dark"
265
168
  // ... other props
266
169
  />
267
170
  ```
268
171
 
269
- ### Image Display Mode
270
-
271
- ```tsx
272
- <TvosSearchView
273
- imageContentMode="fit" // 'fill' (crop), 'fit'/'contain' (letterbox)
274
- // ... other props
275
- />
276
- ```
172
+ - `"dark"` white text, dark UI elements (good for dark-background apps)
173
+ - `"light"` — black text, light UI elements
174
+ - `"system"` — follows the device setting (default, no override)
277
175
 
278
- <p align="center">
279
- <img src="screenshots/results.png" alt="Results for search screen using expo-tvos-search" style="border-radius: 16px;max-width: 100%;"/><br/>
280
- </p>
176
+ ## API Reference
281
177
 
282
- ## Props
178
+ ### Props
283
179
 
284
- ### Core Props
180
+ #### Core
285
181
 
286
182
  | Prop | Type | Default | Description |
287
183
  |------|------|---------|-------------|
288
- | `results` | `SearchResult[]` | `[]` | Array of search results |
289
- | `columns` | `number` | `5` | Number of columns in the grid |
290
- | `placeholder` | `string` | `"Search..."` | Search field placeholder |
291
- | `searchText` | `string` | — | Programmatically set search field text (restore state, deep links) |
292
- | `isLoading` | `boolean` | `false` | Shows loading indicator |
184
+ | `results` | `SearchResult[]` | `[]` | Array of search results to display. Capped at 500 items. |
185
+ | `columns` | `number` | `5` | Number of grid columns (clamped 1–10) |
186
+ | `placeholder` | `string` | `"Search..."` | Search field placeholder text |
187
+ | `searchText` | `string` | — | Programmatically set search field text (for deep links, state restore) |
188
+ | `isLoading` | `boolean` | `false` | Shows a loading indicator |
293
189
 
294
- ### Card Dimensions & Spacing
190
+ #### Card Dimensions & Spacing
295
191
 
296
192
  | Prop | Type | Default | Description |
297
193
  |------|------|---------|-------------|
298
- | `cardWidth` | `number` | `280` | Width of each result card in points |
299
- | `cardHeight` | `number` | `420` | Height of each result card in points |
300
- | `cardMargin` | `number` | `40` | **(v1.3.0+)** Spacing between cards in the grid (horizontal and vertical) |
301
- | `cardPadding` | `number` | `16` | **(v1.3.0+)** Padding inside the card for overlay content (title/subtitle) |
302
- | `topInset` | `number` | `0` | Top padding (for tab bar clearance) |
194
+ | `cardWidth` | `number` | `280` | Width of each result card in points (clamped 50–1000) |
195
+ | `cardHeight` | `number` | `420` | Height of each result card in points (clamped 50–1000) |
196
+ | `cardMargin` | `number` | `40` | Spacing between cards (horizontal and vertical, clamped 0–200) |
197
+ | `cardPadding` | `number` | `16` | Padding inside the card for overlay content (clamped 0–100) |
198
+ | `topInset` | `number` | `0` | Top padding for tab bar clearance (clamped 0–500) |
303
199
 
304
- ### Display Options
200
+ #### Display Options
305
201
 
306
202
  | Prop | Type | Default | Description |
307
203
  |------|------|---------|-------------|
308
- | `showTitle` | `boolean` | `false` | Show title below each result |
204
+ | `showTitle` | `boolean` | `false` | Show title below each result card |
309
205
  | `showSubtitle` | `boolean` | `false` | Show subtitle below title |
310
206
  | `showTitleOverlay` | `boolean` | `true` | Show title overlay with gradient at bottom of card |
311
207
  | `showFocusBorder` | `boolean` | `false` | Show border on focused item |
312
- | `imageContentMode` | `'fill' \| 'fit' \| 'contain'` | `'fill'` | How images fill the card: `fill` (crop to fill), `fit`/`contain` (letterbox) |
208
+ | `imageContentMode` | `'fill' \| 'fit' \| 'contain'` | `'fill'` | How images fill the card: `fill` crops, `fit`/`contain` letterbox |
313
209
 
314
- ### Styling & Colors
210
+ #### Styling & Colors
315
211
 
316
212
  | Prop | Type | Default | Description |
317
213
  |------|------|---------|-------------|
318
- | `textColor` | `string` | system default | Color for text and UI elements (hex format, e.g., "#FFFFFF") |
319
- | `accentColor` | `string` | `"#FFC312"` | Accent color for focused elements (hex format, e.g., "#FFC312") |
320
- | `overlayTitleSize` | `number` | `20` | **(v1.3.0+)** Font size for title text in the blur overlay (when showTitleOverlay is true) |
214
+ | `textColor` | `string` | system default | Color for text and UI elements (hex, e.g. `"#FFFFFF"`) |
215
+ | `accentColor` | `string` | `"#FFC312"` | Accent color for focused elements (hex, e.g. `"#E50914"`) |
216
+ | `colorScheme` | `'light' \| 'dark' \| 'system'` | `"system"` | Override the system color scheme for the search view |
217
+ | `overlayTitleSize` | `number` | `20` | Font size for title text in the blur overlay (clamped 8–72) |
321
218
 
322
- ### Animation
219
+ #### Animation
323
220
 
324
221
  | Prop | Type | Default | Description |
325
222
  |------|------|---------|-------------|
326
223
  | `enableMarquee` | `boolean` | `true` | Enable marquee scrolling for long titles |
327
- | `marqueeDelay` | `number` | `1.5` | Delay in seconds before marquee starts |
224
+ | `marqueeDelay` | `number` | `1.5` | Delay in seconds before marquee starts (clamped 0–60) |
328
225
 
329
- ### Text Customization
226
+ #### Text Customization
330
227
 
331
228
  | Prop | Type | Default | Description |
332
229
  |------|------|---------|-------------|
333
230
  | `emptyStateText` | `string` | `"Search your library"` | Text shown when search field is empty |
334
231
  | `searchingText` | `string` | `"Searching..."` | Text shown during search |
335
- | `noResultsText` | `string` | `"No results found"` | Text shown when no results found |
232
+ | `noResultsText` | `string` | `"No results found"` | Text shown when no results match |
336
233
  | `noResultsHintText` | `string` | `"Try a different search term"` | Hint text below no results message |
337
234
 
338
- ### Event Handlers
235
+ #### Event Handlers
339
236
 
340
- | Prop | Type | Default | Description |
341
- |------|------|---------|-------------|
342
- | `onSearch` | `function` | required | Called when search text changes |
343
- | `onSelectItem` | `function` | required | Called when result is selected |
344
- | `onError` | `function` | optional | **(v1.2.0+)** Called when errors occur (image loading failures, validation errors) |
345
- | `onValidationWarning` | `function` | optional | **(v1.2.0+)** Called for non-fatal warnings (truncated fields, clamped values, invalid URLs) |
346
- | `onSearchFieldFocused` | `function` | optional | **(v1.3.2+)** Called when native search field gains focus. Use with `TVEventControl` for Apple TV hardware keyboard support. |
347
- | `onSearchFieldBlurred` | `function` | optional | **(v1.3.2+)** Called when native search field loses focus. Use to re-enable gesture handlers. |
237
+ | Prop | Type | Required | Description |
238
+ |------|------|----------|-------------|
239
+ | `onSearch` | `(event: SearchEvent) => void` | Yes | Called when search text changes |
240
+ | `onSelectItem` | `(event: SelectItemEvent) => void` | Yes | Called when a result is selected |
241
+ | `onError` | `(event: SearchViewErrorEvent) => void` | No | Called on errors (image loading, validation) |
242
+ | `onValidationWarning` | `(event: ValidationWarningEvent) => void` | No | Called for non-fatal warnings (truncated fields, clamped values) |
243
+ | `onSearchFieldFocused` | `(event: SearchFieldFocusEvent) => void` | No | Called when native search field gains focus |
244
+ | `onSearchFieldBlurred` | `(event: SearchFieldFocusEvent) => void` | No | Called when native search field loses focus |
348
245
 
349
- ### Other
246
+ #### Other
350
247
 
351
248
  | Prop | Type | Default | Description |
352
249
  |------|------|---------|-------------|
353
- | `style` | `ViewStyle` | optional | Style object for the view container |
250
+ | `style` | `ViewStyle` | | Style object for the view container |
354
251
 
355
- ## Result Handling
252
+ ### SearchResult
356
253
 
357
- The native implementation applies the following validation and constraints:
254
+ ```ts
255
+ interface SearchResult {
256
+ id: string; // Unique identifier (used in onSelectItem)
257
+ title: string; // Primary display text
258
+ subtitle?: string; // Optional secondary text
259
+ imageUrl?: string; // Optional poster/thumbnail URL (HTTPS, HTTP, or data: URI)
260
+ }
261
+ ```
358
262
 
359
- - **Maximum results**: The results array is capped at 500 items. Any results beyond this limit are silently ignored.
360
- - **Required fields**: Results with empty `id` or `title` are automatically filtered out and not displayed.
361
- - **Image URL schemes**: HTTP, HTTPS, and `data:` URIs are accepted for `imageUrl`. Other URL schemes (e.g., `file://`) are rejected.
362
- - **HTTPS recommended**: HTTP URLs may be blocked by App Transport Security on tvOS unless explicitly allowed in Info.plist.
263
+ ### isNativeSearchAvailable()
363
264
 
364
- ## Testing
265
+ ```ts
266
+ function isNativeSearchAvailable(): boolean
267
+ ```
268
+
269
+ Returns `true` when running on tvOS with the native module properly built. Use this to conditionally render a fallback on non-tvOS platforms.
365
270
 
366
- Run TypeScript tests:
271
+ ```tsx
272
+ import { TvosSearchView, isNativeSearchAvailable } from 'expo-tvos-search';
273
+
274
+ if (!isNativeSearchAvailable()) {
275
+ return <FallbackSearch />;
276
+ }
277
+ return <TvosSearchView {...props} />;
278
+ ```
279
+
280
+ ## Result Validation
281
+
282
+ The native implementation applies the following constraints:
283
+
284
+ - **Maximum results** — the array is capped at 500 items; extras are silently ignored
285
+ - **Required fields** — results with empty `id` or `title` are filtered out
286
+ - **Image URL schemes** — HTTP, HTTPS, and `data:` URIs are accepted; other schemes (e.g. `file://`) are rejected
287
+ - **HTTPS recommended** — HTTP URLs may be blocked by App Transport Security unless explicitly allowed in Info.plist
288
+
289
+ ## Demo App
290
+
291
+ Explore all configurations in the [expo-tvos-search-demo](https://github.com/keiver/expo-tvos-search-demo) repository.
292
+
293
+ ## Testing
367
294
 
368
295
  ```bash
369
296
  npm test # Run tests once
@@ -373,7 +300,7 @@ npm run test:coverage # Generate coverage report
373
300
 
374
301
  Tests cover:
375
302
 
376
- - `isNativeSearchAvailable()` behavior on different platforms
303
+ - Behavior across platforms
377
304
  - Component rendering when native module is unavailable
378
305
  - Event structure validation
379
306
 
@@ -387,10 +314,25 @@ We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guid
387
314
  - Commit message conventions
388
315
  - Pull request process
389
316
 
390
- ### Adding New Props
317
+ <table>
318
+ <tr>
319
+ <td><img src="screenshots/default.webp" alt="Default empty search state with tvOS keyboard and tab bar" width="100%"/></td>
320
+ <td><img src="screenshots/demo-01.webp" alt="Square card grid layout with planet images and no title overlay" width="100%"/></td>
321
+ <td><img src="screenshots/demo-02.webp" alt="Four-column grid with title overlays and hardware keyboard active indicator" width="100%"/></td>
322
+ </tr>
323
+ <tr>
324
+ <td><img src="screenshots/demo-03.webp" alt="Five-column compact grid with short overlay titles" width="100%"/></td>
325
+ <td><img src="screenshots/demo-04.webp" alt="Mini card grid with truncated overlay titles on planet cards" width="100%"/></td>
326
+ <td><img src="screenshots/demo-06.webp" alt="Demo app home screen with feature badges and configuration menu" width="100%"/></td>
327
+ </tr>
328
+ <tr>
329
+ <td><img src="screenshots/no-results.webp" alt="No results found state after searching for a term with no matches" width="100%"/></td>
330
+ <td><img src="screenshots/results-jungle-book.webp" alt="Single search result for Jungle Book with poster card and title overlay" width="100%"/></td>
331
+ <td><img src="screenshots/results.webp" alt="Focused search result card for Caminandes with accent color border" width="100%"/></td>
332
+ </tr>
333
+ </table>
391
334
 
392
- 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.
393
335
 
394
336
  ## License
395
337
 
396
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
338
+ MIT see [LICENSE](LICENSE) for details.
package/build/index.d.ts CHANGED
@@ -206,17 +206,34 @@ export interface TvosSearchViewProps {
206
206
  * @example "#E50914" for Netflix red
207
207
  */
208
208
  accentColor?: string;
209
+ /**
210
+ * Override the system color scheme for the search view.
211
+ * - `'dark'`: Force dark appearance (white text, dark UI elements)
212
+ * - `'light'`: Force light appearance (black text, light UI elements)
213
+ * - `'system'`: Follow the system appearance (default)
214
+ *
215
+ * Useful when your app has a fixed dark background and the system is in
216
+ * light mode, which would make search bar text illegible.
217
+ * @default "system"
218
+ */
219
+ colorScheme?: 'light' | 'dark' | 'system';
209
220
  /**
210
221
  * Width of each result card in points.
211
222
  * Allows customization for portrait, landscape, or square layouts.
223
+ * Values outside 50-1000 range are clamped.
212
224
  * @default 280
225
+ * @minimum 50
226
+ * @maximum 1000
213
227
  * @example 420 for landscape cards
214
228
  */
215
229
  cardWidth?: number;
216
230
  /**
217
231
  * Height of each result card in points.
218
232
  * Allows customization for portrait, landscape, or square layouts.
233
+ * Values outside 50-1000 range are clamped.
219
234
  * @default 420
235
+ * @minimum 50
236
+ * @maximum 1000
220
237
  * @example 240 for landscape cards (16:9 ratio with width=420)
221
238
  */
222
239
  cardHeight?: number;
@@ -230,20 +247,29 @@ export interface TvosSearchViewProps {
230
247
  imageContentMode?: 'fill' | 'fit' | 'contain';
231
248
  /**
232
249
  * Spacing between cards in the grid layout (both horizontal and vertical).
250
+ * Values outside 0-200 range are clamped.
233
251
  * @default 40
252
+ * @minimum 0
253
+ * @maximum 200
234
254
  * @example 60 for spacious layouts, 20 for compact grids
235
255
  */
236
256
  cardMargin?: number;
237
257
  /**
238
258
  * Padding inside the card for overlay content (title, subtitle).
259
+ * Values outside 0-100 range are clamped.
239
260
  * @default 16
261
+ * @minimum 0
262
+ * @maximum 100
240
263
  * @example 20 for more breathing room, 12 for compact cards
241
264
  */
242
265
  cardPadding?: number;
243
266
  /**
244
267
  * Font size for title in the blur overlay (when showTitleOverlay is true).
245
268
  * Allows customization of overlay text size for different card layouts.
269
+ * Values outside 8-72 range are clamped.
246
270
  * @default 20
271
+ * @minimum 8
272
+ * @maximum 72
247
273
  * @example 18 for smaller cards, 24 for larger cards
248
274
  */
249
275
  overlayTitleSize?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'ExpoTvosSearch'
3
- s.version = '1.0.0'
3
+ s.version = '1.5.0'
4
4
  s.summary = 'Native tvOS search view with SwiftUI .searchable modifier'
5
5
  s.description = 'Provides a native tvOS search experience using SwiftUI searchable modifier for proper focus and keyboard navigation'
6
6
  s.author = 'Keiver Hernandez'
@@ -143,6 +143,10 @@ public class ExpoTvosSearchModule: Module {
143
143
  view.accentColor = colorHex
144
144
  }
145
145
 
146
+ Prop("colorScheme") { (view: ExpoTvosSearchView, scheme: String) in
147
+ view.colorScheme = scheme
148
+ }
149
+
146
150
  Prop("cardWidth") { (view: ExpoTvosSearchView, width: Double) in
147
151
  view.cardWidth = CGFloat(max(50, min(1000, width))) // Clamp to reasonable range
148
152
  }
@@ -178,6 +178,12 @@ class ExpoTvosSearchView: ExpoView {
178
178
  }
179
179
  }
180
180
 
181
+ var colorScheme: String = "system" {
182
+ didSet {
183
+ applyColorScheme()
184
+ }
185
+ }
186
+
181
187
  var cardWidth: CGFloat = 280 {
182
188
  didSet {
183
189
  viewModel.cardWidth = cardWidth
@@ -249,12 +255,28 @@ class ExpoTvosSearchView: ExpoView {
249
255
  }
250
256
  }
251
257
 
258
+ private func applyColorScheme() {
259
+ let style: UIUserInterfaceStyle
260
+ switch colorScheme.lowercased() {
261
+ case "dark":
262
+ style = .dark
263
+ case "light":
264
+ style = .light
265
+ default:
266
+ style = .unspecified
267
+ }
268
+ hostingController?.overrideUserInterfaceStyle = style
269
+ }
270
+
252
271
  private func setupView() {
253
272
  let contentView = TvosSearchContentView(viewModel: viewModel)
254
273
  let controller = UIHostingController(rootView: contentView)
255
274
  controller.view.backgroundColor = .clear
256
275
  hostingController = controller
257
276
 
277
+ // Apply initial color scheme (default "system" → .unspecified)
278
+ applyColorScheme()
279
+
258
280
  // Configure viewModel callbacks
259
281
  viewModel.onSearch = { [weak self] query in
260
282
  self?.onSearch(["query": query])
@@ -557,6 +579,7 @@ class ExpoTvosSearchView: ExpoView {
557
579
  var noResultsText: String = "No results found"
558
580
  var noResultsHintText: String = "Try a different search term"
559
581
  var textColor: String? = nil
582
+ var colorScheme: String = "system"
560
583
  var accentColor: String = "#FFC312"
561
584
  var cardWidth: CGFloat = 280
562
585
  var cardHeight: CGFloat = 420
@@ -36,6 +36,7 @@ struct TvosSearchContentView: View {
36
36
  viewModel.onSearch?(newValue)
37
37
  }
38
38
  }
39
+ .tint(viewModel.accentColor)
39
40
  .padding(.top, viewModel.topInset)
40
41
  .ignoresSafeArea(.all, edges: .top)
41
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-tvos-search",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
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",
package/src/index.tsx CHANGED
@@ -237,10 +237,25 @@ export interface TvosSearchViewProps {
237
237
  */
238
238
  accentColor?: string;
239
239
 
240
+ /**
241
+ * Override the system color scheme for the search view.
242
+ * - `'dark'`: Force dark appearance (white text, dark UI elements)
243
+ * - `'light'`: Force light appearance (black text, light UI elements)
244
+ * - `'system'`: Follow the system appearance (default)
245
+ *
246
+ * Useful when your app has a fixed dark background and the system is in
247
+ * light mode, which would make search bar text illegible.
248
+ * @default "system"
249
+ */
250
+ colorScheme?: 'light' | 'dark' | 'system';
251
+
240
252
  /**
241
253
  * Width of each result card in points.
242
254
  * Allows customization for portrait, landscape, or square layouts.
255
+ * Values outside 50-1000 range are clamped.
243
256
  * @default 280
257
+ * @minimum 50
258
+ * @maximum 1000
244
259
  * @example 420 for landscape cards
245
260
  */
246
261
  cardWidth?: number;
@@ -248,7 +263,10 @@ export interface TvosSearchViewProps {
248
263
  /**
249
264
  * Height of each result card in points.
250
265
  * Allows customization for portrait, landscape, or square layouts.
266
+ * Values outside 50-1000 range are clamped.
251
267
  * @default 420
268
+ * @minimum 50
269
+ * @maximum 1000
252
270
  * @example 240 for landscape cards (16:9 ratio with width=420)
253
271
  */
254
272
  cardHeight?: number;
@@ -264,14 +282,20 @@ export interface TvosSearchViewProps {
264
282
 
265
283
  /**
266
284
  * Spacing between cards in the grid layout (both horizontal and vertical).
285
+ * Values outside 0-200 range are clamped.
267
286
  * @default 40
287
+ * @minimum 0
288
+ * @maximum 200
268
289
  * @example 60 for spacious layouts, 20 for compact grids
269
290
  */
270
291
  cardMargin?: number;
271
292
 
272
293
  /**
273
294
  * Padding inside the card for overlay content (title, subtitle).
295
+ * Values outside 0-100 range are clamped.
274
296
  * @default 16
297
+ * @minimum 0
298
+ * @maximum 100
275
299
  * @example 20 for more breathing room, 12 for compact cards
276
300
  */
277
301
  cardPadding?: number;
@@ -279,7 +303,10 @@ export interface TvosSearchViewProps {
279
303
  /**
280
304
  * Font size for title in the blur overlay (when showTitleOverlay is true).
281
305
  * Allows customization of overlay text size for different card layouts.
306
+ * Values outside 8-72 range are clamped.
282
307
  * @default 20
308
+ * @minimum 8
309
+ * @maximum 72
283
310
  * @example 18 for smaller cards, 24 for larger cards
284
311
  */
285
312
  overlayTitleSize?: number;