@indxsearch/intrface 1.0.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/README.md +471 -0
- package/dist/index.es.js +1705 -0
- package/dist/index.umd.js +22 -0
- package/dist/intrface.css +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
# @indxsearch/intrface
|
|
2
|
+
|
|
3
|
+
A powerful, flexible React search UI library for INDX Search API.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔍 **Full-text search** with fuzzy matching and typo tolerance
|
|
8
|
+
- 🎯 **Faceted filtering** - Value filters (exact match) and range filters (numeric)
|
|
9
|
+
- 📊 **Real-time facet counts** - Dynamic aggregations that update with search
|
|
10
|
+
- 📱 **Mobile-responsive** - Built-in responsive design
|
|
11
|
+
- ⚡ **Debounced searches** - Optimized performance
|
|
12
|
+
- 🎨 **Customizable rendering** - Full control over result display
|
|
13
|
+
- 🔒 **Secure authentication** - Session-based authentication with automatic login
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @indxsearch/intrface @indxsearch/systm @indxsearch/pixl
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### 1. Set Up Environment Variables
|
|
24
|
+
|
|
25
|
+
Create a `.env.local` file in your project root with your INDX credentials:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# INDX Server Configuration
|
|
29
|
+
NEXT_PUBLIC_INDX_URL=https://your-indx-server.com
|
|
30
|
+
|
|
31
|
+
# Authentication Credentials
|
|
32
|
+
NEXT_PUBLIC_INDX_EMAIL=your@email.com
|
|
33
|
+
NEXT_PUBLIC_INDX_PASSWORD=yourpassword
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**For local development:**
|
|
37
|
+
```bash
|
|
38
|
+
NEXT_PUBLIC_INDX_URL=http://localhost:38171
|
|
39
|
+
NEXT_PUBLIC_INDX_EMAIL=your@email.com
|
|
40
|
+
NEXT_PUBLIC_INDX_PASSWORD=yourpassword
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Security Notes:**
|
|
44
|
+
- Never commit `.env.local` to version control
|
|
45
|
+
- Store credentials securely in environment variables
|
|
46
|
+
- The library automatically calls the Login API on initialization to get a fresh session token
|
|
47
|
+
- Session tokens are managed internally and refreshed as needed
|
|
48
|
+
|
|
49
|
+
### 2. Import Styles
|
|
50
|
+
|
|
51
|
+
Import the CSS file in your app entry point:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import '@indxsearch/intrface/styles.css';
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 3. Basic Implementation
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
'use client';
|
|
61
|
+
import { SearchProvider, SearchInput, SearchResults } from '@indxsearch/intrface';
|
|
62
|
+
|
|
63
|
+
export default function SearchPage() {
|
|
64
|
+
return (
|
|
65
|
+
<SearchProvider
|
|
66
|
+
url={process.env.NEXT_PUBLIC_INDX_URL!}
|
|
67
|
+
email={process.env.NEXT_PUBLIC_INDX_EMAIL!}
|
|
68
|
+
password={process.env.NEXT_PUBLIC_INDX_PASSWORD!}
|
|
69
|
+
dataset="products"
|
|
70
|
+
>
|
|
71
|
+
<SearchInput placeholder="Search products..." />
|
|
72
|
+
|
|
73
|
+
<SearchResults
|
|
74
|
+
fields={['name', 'description', 'category']}
|
|
75
|
+
resultsPerPage={10}
|
|
76
|
+
>
|
|
77
|
+
{(item) => (
|
|
78
|
+
<div>
|
|
79
|
+
<h3>{item.name}</h3>
|
|
80
|
+
<p>{item.description}</p>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
</SearchResults>
|
|
84
|
+
</SearchProvider>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Using different datasets on different pages:**
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// products page
|
|
93
|
+
<SearchProvider url={url} email={email} password={password} dataset="products">
|
|
94
|
+
{/* ... */}
|
|
95
|
+
</SearchProvider>
|
|
96
|
+
|
|
97
|
+
// articles page
|
|
98
|
+
<SearchProvider url={url} email={email} password={password} dataset="articles">
|
|
99
|
+
{/* ... */}
|
|
100
|
+
</SearchProvider>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Authentication
|
|
104
|
+
|
|
105
|
+
The library uses **session-based authentication** that automatically logs in when the app initializes.
|
|
106
|
+
|
|
107
|
+
### How It Works
|
|
108
|
+
|
|
109
|
+
1. You provide your email and password to `SearchProvider`
|
|
110
|
+
2. On mount, the library automatically calls the Login API endpoint
|
|
111
|
+
3. A fresh session token is obtained and used for all subsequent requests
|
|
112
|
+
4. No need to manually manage tokens - it's all handled internally
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
<SearchProvider
|
|
116
|
+
url="https://your-indx-server.com"
|
|
117
|
+
email="your@email.com"
|
|
118
|
+
password="yourpassword"
|
|
119
|
+
dataset="products"
|
|
120
|
+
>
|
|
121
|
+
{/* Your search UI */}
|
|
122
|
+
</SearchProvider>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Authentication Benefits:**
|
|
126
|
+
- ✅ Automatic login on app initialization
|
|
127
|
+
- ✅ Fresh session tokens on every app load
|
|
128
|
+
- ✅ No manual token management required
|
|
129
|
+
- ✅ Works reliably after server restarts
|
|
130
|
+
|
|
131
|
+
**Security Best Practices:**
|
|
132
|
+
- Store credentials in environment variables (`.env.local`)
|
|
133
|
+
- Never commit credentials to version control
|
|
134
|
+
- Use secure HTTPS connections in production
|
|
135
|
+
|
|
136
|
+
## Error Handling
|
|
137
|
+
|
|
138
|
+
The library includes comprehensive error handling with helpful console messages:
|
|
139
|
+
|
|
140
|
+
### Automatic Error Detection
|
|
141
|
+
|
|
142
|
+
The SearchProvider automatically validates:
|
|
143
|
+
- ✅ Authentication credentials (email/password)
|
|
144
|
+
- ✅ Login success and token retrieval
|
|
145
|
+
- ✅ Dataset existence and status
|
|
146
|
+
- ✅ Dataset readiness (indexing complete)
|
|
147
|
+
- ✅ Empty dataset warnings
|
|
148
|
+
- ✅ Network connectivity
|
|
149
|
+
|
|
150
|
+
All errors include:
|
|
151
|
+
- Clear error messages
|
|
152
|
+
- Specific problem identification
|
|
153
|
+
- Actionable fix suggestions
|
|
154
|
+
- Example commands to resolve issues
|
|
155
|
+
|
|
156
|
+
### Error Boundary (Optional)
|
|
157
|
+
|
|
158
|
+
Wrap your search interface with `SearchErrorBoundary` for graceful error handling:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { SearchErrorBoundary, SearchProvider } from '@indxsearch/intrface';
|
|
162
|
+
|
|
163
|
+
<SearchErrorBoundary>
|
|
164
|
+
<SearchProvider url={url} email={email} password={password} dataset={dataset}>
|
|
165
|
+
{/* Your search UI */}
|
|
166
|
+
</SearchProvider>
|
|
167
|
+
</SearchErrorBoundary>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Custom error UI:**
|
|
171
|
+
```typescript
|
|
172
|
+
<SearchErrorBoundary
|
|
173
|
+
fallback={(error, reset) => (
|
|
174
|
+
<div>
|
|
175
|
+
<h2>Search Error</h2>
|
|
176
|
+
<p>{error.message}</p>
|
|
177
|
+
<button onClick={reset}>Try Again</button>
|
|
178
|
+
</div>
|
|
179
|
+
)}
|
|
180
|
+
>
|
|
181
|
+
<SearchProvider url={url} email={email} password={password} dataset={dataset}>
|
|
182
|
+
{children}
|
|
183
|
+
</SearchProvider>
|
|
184
|
+
</SearchErrorBoundary>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Console Error Messages
|
|
188
|
+
|
|
189
|
+
All errors show in the browser console with emoji indicators:
|
|
190
|
+
- ✅ = Success
|
|
191
|
+
- 🔍 = Checking something
|
|
192
|
+
- ⚠️ = Warning (non-critical)
|
|
193
|
+
- ❌ = Error (needs fixing)
|
|
194
|
+
- 💡 = Helpful suggestion
|
|
195
|
+
|
|
196
|
+
**Example:**
|
|
197
|
+
```
|
|
198
|
+
[Auth] ❌ Dataset "products" not found (404)
|
|
199
|
+
[Auth] 💡 Available datasets can be checked with: curl -X GET ...
|
|
200
|
+
[Auth] 💡 Make sure you spelled the dataset name correctly
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Adding Filters
|
|
204
|
+
|
|
205
|
+
### Value Filters (Exact Match)
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { ValueFilterPanel } from '@indxsearch/intrface';
|
|
209
|
+
|
|
210
|
+
<SearchProvider {...authProps}>
|
|
211
|
+
<SearchInput />
|
|
212
|
+
|
|
213
|
+
{/* Simple checkbox list */}
|
|
214
|
+
<ValueFilterPanel
|
|
215
|
+
field="category"
|
|
216
|
+
label="Category"
|
|
217
|
+
/>
|
|
218
|
+
|
|
219
|
+
{/* Button-style filters */}
|
|
220
|
+
<ValueFilterPanel
|
|
221
|
+
field="brand"
|
|
222
|
+
label="Brand"
|
|
223
|
+
displayType="button"
|
|
224
|
+
layout="grid"
|
|
225
|
+
/>
|
|
226
|
+
|
|
227
|
+
<SearchResults {...resultsProps}>
|
|
228
|
+
{renderItem}
|
|
229
|
+
</SearchResults>
|
|
230
|
+
</SearchProvider>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Range Filters (Numeric)
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { RangeFilterPanel } from '@indxsearch/intrface';
|
|
237
|
+
|
|
238
|
+
<RangeFilterPanel
|
|
239
|
+
field="price"
|
|
240
|
+
label="Price Range"
|
|
241
|
+
min={0}
|
|
242
|
+
max={1000}
|
|
243
|
+
/>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Active Filters Display
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { ActiveFiltersPanel } from '@indxsearch/intrface';
|
|
250
|
+
|
|
251
|
+
<ActiveFiltersPanel />
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Full Example with Filters
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
'use client';
|
|
258
|
+
import {
|
|
259
|
+
SearchProvider,
|
|
260
|
+
SearchInput,
|
|
261
|
+
SearchResults,
|
|
262
|
+
ValueFilterPanel,
|
|
263
|
+
RangeFilterPanel,
|
|
264
|
+
ActiveFiltersPanel,
|
|
265
|
+
SortByPanel,
|
|
266
|
+
} from '@indxsearch/intrface';
|
|
267
|
+
|
|
268
|
+
export default function AdvancedSearch() {
|
|
269
|
+
return (
|
|
270
|
+
<SearchProvider
|
|
271
|
+
url={process.env.NEXT_PUBLIC_INDX_URL!}
|
|
272
|
+
email={process.env.NEXT_PUBLIC_INDX_EMAIL!}
|
|
273
|
+
password={process.env.NEXT_PUBLIC_INDX_PASSWORD!}
|
|
274
|
+
dataset="products"
|
|
275
|
+
allowEmptySearch={true}
|
|
276
|
+
enableFacets={true}
|
|
277
|
+
maxResults={20}
|
|
278
|
+
>
|
|
279
|
+
<div style={{ display: 'flex', gap: '2rem' }}>
|
|
280
|
+
{/* Sidebar with filters */}
|
|
281
|
+
<aside style={{ width: '250px' }}>
|
|
282
|
+
<ActiveFiltersPanel />
|
|
283
|
+
<SortByPanel displayType="radio" />
|
|
284
|
+
<ValueFilterPanel field="category" label="Category" />
|
|
285
|
+
<ValueFilterPanel field="brand" label="Brand" displayType="button" />
|
|
286
|
+
<RangeFilterPanel field="price" label="Price" />
|
|
287
|
+
</aside>
|
|
288
|
+
|
|
289
|
+
{/* Main content */}
|
|
290
|
+
<main style={{ flex: 1 }}>
|
|
291
|
+
<SearchInput placeholder="Search products..." showFocus={true} />
|
|
292
|
+
|
|
293
|
+
<SearchResults
|
|
294
|
+
fields={['name', 'description', 'price', 'category', 'brand']}
|
|
295
|
+
resultsPerPage={20}
|
|
296
|
+
>
|
|
297
|
+
{(item) => (
|
|
298
|
+
<div style={{ padding: '1rem', borderBottom: '1px solid #eee' }}>
|
|
299
|
+
<h3>{item.name}</h3>
|
|
300
|
+
<p>{item.description}</p>
|
|
301
|
+
<div>
|
|
302
|
+
<strong>${item.price}</strong> • {item.category}
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
)}
|
|
306
|
+
</SearchResults>
|
|
307
|
+
</main>
|
|
308
|
+
</div>
|
|
309
|
+
</SearchProvider>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## API Reference
|
|
315
|
+
|
|
316
|
+
### SearchProvider Props
|
|
317
|
+
|
|
318
|
+
| Prop | Type | Required | Default | Description |
|
|
319
|
+
|------|------|----------|---------|-------------|
|
|
320
|
+
| `url` | `string` | ✅ | - | INDX server URL |
|
|
321
|
+
| `email` | `string` | ✅ | - | User email for authentication |
|
|
322
|
+
| `password` | `string` | ✅ | - | User password for authentication |
|
|
323
|
+
| `dataset` | `string` | ✅ | - | Dataset name |
|
|
324
|
+
| `allowEmptySearch` | `boolean` | ❌ | `false` | Show results without query |
|
|
325
|
+
| `enableFacets` | `boolean` | ❌ | `true` | Enable faceted search |
|
|
326
|
+
| `maxResults` | `number` | ❌ | `10` | Max results per search |
|
|
327
|
+
| `facetDebounceDelayMillis` | `number` | ❌ | `500` | Debounce delay for facet updates |
|
|
328
|
+
| `coverageDepth` | `number` | ❌ | `500` | Search depth for fuzzy matching |
|
|
329
|
+
| `removeDuplicates` | `boolean` | ❌ | `false` | Remove duplicate results |
|
|
330
|
+
|
|
331
|
+
### SearchInput Props
|
|
332
|
+
|
|
333
|
+
| Prop | Type | Default | Description |
|
|
334
|
+
|------|------|---------|-------------|
|
|
335
|
+
| `placeholder` | `string` | `'Search...'` | Input placeholder text |
|
|
336
|
+
| `showClear` | `boolean` | `true` | Show clear button |
|
|
337
|
+
| `showFocus` | `boolean` | `false` | Show focus ring |
|
|
338
|
+
| `inputSize` | `'small' \| 'default' \| 'large'` | `'default'` | Input size |
|
|
339
|
+
|
|
340
|
+
### SearchResults Props
|
|
341
|
+
|
|
342
|
+
| Prop | Type | Required | Description |
|
|
343
|
+
|------|------|----------|-------------|
|
|
344
|
+
| `fields` | `string[]` | ✅ | Document fields to fetch |
|
|
345
|
+
| `resultsPerPage` | `number` | ✅ | Results per page |
|
|
346
|
+
| `children` | `(item: any) => ReactNode` | ✅ | Render function for each result |
|
|
347
|
+
|
|
348
|
+
### ValueFilterPanel Props
|
|
349
|
+
|
|
350
|
+
| Prop | Type | Default | Description |
|
|
351
|
+
|------|------|---------|-------------|
|
|
352
|
+
| `field` | `string` | ✅ | Field name to filter on |
|
|
353
|
+
| `label` | `string` | ❌ | Display label |
|
|
354
|
+
| `displayType` | `'checkbox' \| 'button'` | `'checkbox'` | Filter UI style |
|
|
355
|
+
| `layout` | `'list' \| 'grid'` | `'list'` | Layout style |
|
|
356
|
+
| `limit` | `number` | `undefined` | Max filters to show |
|
|
357
|
+
| `startCollapsed` | `boolean` | `false` | Start collapsed |
|
|
358
|
+
| `showCount` | `boolean` | `true` | Show facet counts |
|
|
359
|
+
|
|
360
|
+
### RangeFilterPanel Props
|
|
361
|
+
|
|
362
|
+
| Prop | Type | Required | Description |
|
|
363
|
+
|------|------|----------|-------------|
|
|
364
|
+
| `field` | `string` | ✅ | Field name to filter on |
|
|
365
|
+
| `label` | `string` | ❌ | Display label |
|
|
366
|
+
| `min` | `number` | ❌ | Minimum value |
|
|
367
|
+
| `max` | `number` | ❌ | Maximum value |
|
|
368
|
+
|
|
369
|
+
## Troubleshooting
|
|
370
|
+
|
|
371
|
+
### "Login failed" error
|
|
372
|
+
|
|
373
|
+
**Problem:** Authentication credentials are invalid
|
|
374
|
+
|
|
375
|
+
**Solutions:**
|
|
376
|
+
1. Verify your email and password are correct
|
|
377
|
+
2. Check that the credentials match your INDX account
|
|
378
|
+
3. Ensure the INDX server URL is correct
|
|
379
|
+
4. Check browser console for detailed error messages
|
|
380
|
+
|
|
381
|
+
### "401 Unauthorized" errors after successful login
|
|
382
|
+
|
|
383
|
+
**Problem:** Session token became invalid
|
|
384
|
+
|
|
385
|
+
**Solutions:**
|
|
386
|
+
1. Refresh the page to get a new session token (automatic login)
|
|
387
|
+
2. Verify the server is running and accessible
|
|
388
|
+
3. Check server logs for authentication issues
|
|
389
|
+
|
|
390
|
+
### "Failed to fetch" errors
|
|
391
|
+
|
|
392
|
+
**Problem:** Cannot connect to INDX server
|
|
393
|
+
|
|
394
|
+
**Solutions:**
|
|
395
|
+
1. Verify the server URL is correct
|
|
396
|
+
2. Check if the server is running (for local: `http://localhost:38171`)
|
|
397
|
+
3. Ensure CORS is configured on the server
|
|
398
|
+
4. Check browser console for detailed error
|
|
399
|
+
|
|
400
|
+
### Results not showing
|
|
401
|
+
|
|
402
|
+
**Problem:** Empty results even with data
|
|
403
|
+
|
|
404
|
+
**Solutions:**
|
|
405
|
+
1. Verify dataset name is correct
|
|
406
|
+
2. Check if dataset is indexed (use GetStatus endpoint)
|
|
407
|
+
3. Ensure fields are configured as indexable/facetable
|
|
408
|
+
4. Try `allowEmptySearch={true}` to see all results
|
|
409
|
+
|
|
410
|
+
### Filters not working
|
|
411
|
+
|
|
412
|
+
**Problem:** Filters don't update results
|
|
413
|
+
|
|
414
|
+
**Solutions:**
|
|
415
|
+
1. Ensure fields are configured as filterable/facetable in your dataset
|
|
416
|
+
2. Check browser console for errors
|
|
417
|
+
3. Verify field names match your dataset
|
|
418
|
+
|
|
419
|
+
## Examples
|
|
420
|
+
|
|
421
|
+
### Example 1: E-commerce Search
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
<SearchProvider url={url} email={email} password={password} dataset="products">
|
|
425
|
+
<div className="search-page">
|
|
426
|
+
<SearchInput placeholder="Search products..." />
|
|
427
|
+
|
|
428
|
+
<div className="filters">
|
|
429
|
+
<ValueFilterPanel field="category" label="Category" />
|
|
430
|
+
<ValueFilterPanel field="brand" label="Brand" displayType="button" />
|
|
431
|
+
<RangeFilterPanel field="price" label="Price" min={0} max={1000} />
|
|
432
|
+
<ValueFilterPanel field="inStock" label="In Stock" />
|
|
433
|
+
</div>
|
|
434
|
+
|
|
435
|
+
<SearchResults fields={['name', 'price', 'image']} resultsPerPage={24}>
|
|
436
|
+
{(product) => (
|
|
437
|
+
<ProductCard
|
|
438
|
+
name={product.name}
|
|
439
|
+
price={product.price}
|
|
440
|
+
image={product.image}
|
|
441
|
+
/>
|
|
442
|
+
)}
|
|
443
|
+
</SearchResults>
|
|
444
|
+
</div>
|
|
445
|
+
</SearchProvider>
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Example 2: Document Search
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
<SearchProvider url={url} email={email} password={password} dataset="documents">
|
|
452
|
+
<SearchInput placeholder="Search documents..." />
|
|
453
|
+
|
|
454
|
+
<ValueFilterPanel field="docType" label="Type" />
|
|
455
|
+
<ValueFilterPanel field="author" label="Author" />
|
|
456
|
+
|
|
457
|
+
<SearchResults fields={['title', 'content', 'date']} resultsPerPage={10}>
|
|
458
|
+
{(doc) => (
|
|
459
|
+
<article>
|
|
460
|
+
<h2>{doc.title}</h2>
|
|
461
|
+
<p>{doc.content.substring(0, 200)}...</p>
|
|
462
|
+
<small>{new Date(doc.date).toLocaleDateString()}</small>
|
|
463
|
+
</article>
|
|
464
|
+
)}
|
|
465
|
+
</SearchResults>
|
|
466
|
+
</SearchProvider>
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
## Support
|
|
470
|
+
|
|
471
|
+
- **Documentation:** [docs.indx.co](https://docs.indx.co)
|