@etoile-dev/react 0.2.3 → 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 +341 -206
- package/dist/Searchbar.d.ts +315 -0
- package/dist/Searchbar.js +207 -0
- package/dist/context.d.ts +57 -0
- package/dist/context.js +32 -0
- package/dist/hooks/useEtoileSearch.d.ts +122 -0
- package/dist/hooks/useEtoileSearch.js +138 -0
- package/dist/index.d.ts +44 -19
- package/dist/index.js +37 -12
- package/dist/primitives/Content.d.ts +34 -0
- package/dist/primitives/Content.js +108 -0
- package/dist/primitives/Empty.d.ts +25 -0
- package/dist/primitives/Empty.js +25 -0
- package/dist/primitives/Error.d.ts +29 -0
- package/dist/primitives/Error.js +26 -0
- package/dist/primitives/Group.d.ts +30 -0
- package/dist/primitives/Group.js +22 -0
- package/dist/primitives/Icon.d.ts +21 -0
- package/dist/primitives/Icon.js +14 -0
- package/dist/primitives/Input.d.ts +32 -0
- package/dist/primitives/Input.js +70 -0
- package/dist/primitives/Item.d.ts +61 -0
- package/dist/primitives/Item.js +76 -0
- package/dist/primitives/Kbd.d.ts +20 -0
- package/dist/primitives/Kbd.js +13 -0
- package/dist/primitives/List.d.ts +35 -0
- package/dist/primitives/List.js +37 -0
- package/dist/primitives/Loading.d.ts +25 -0
- package/dist/primitives/Loading.js +26 -0
- package/dist/primitives/Modal.d.ts +39 -0
- package/dist/primitives/Modal.js +37 -0
- package/dist/primitives/ModalInput.d.ts +61 -0
- package/dist/primitives/ModalInput.js +33 -0
- package/dist/primitives/Overlay.d.ts +21 -0
- package/dist/primitives/Overlay.js +41 -0
- package/dist/primitives/Portal.d.ts +28 -0
- package/dist/primitives/Portal.js +30 -0
- package/dist/primitives/Root.d.ts +116 -0
- package/dist/primitives/Root.js +413 -0
- package/dist/primitives/Separator.d.ts +19 -0
- package/dist/primitives/Separator.js +18 -0
- package/dist/primitives/Thumbnail.d.ts +31 -0
- package/dist/primitives/Thumbnail.js +59 -0
- package/dist/primitives/Trigger.d.ts +28 -0
- package/dist/primitives/Trigger.js +35 -0
- package/dist/store.d.ts +38 -0
- package/dist/store.js +63 -0
- package/dist/styles.css +480 -133
- package/dist/types.d.ts +3 -31
- package/dist/utils/composeRefs.d.ts +12 -0
- package/dist/utils/composeRefs.js +27 -0
- package/dist/utils/slot.d.ts +22 -0
- package/dist/utils/slot.js +58 -0
- package/package.json +9 -5
- package/dist/Search.d.ts +0 -39
- package/dist/Search.js +0 -31
- package/dist/components/SearchIcon.d.ts +0 -22
- package/dist/components/SearchIcon.js +0 -17
- package/dist/components/SearchInput.d.ts +0 -30
- package/dist/components/SearchInput.js +0 -59
- package/dist/components/SearchKbd.d.ts +0 -30
- package/dist/components/SearchKbd.js +0 -24
- package/dist/components/SearchResult.d.ts +0 -31
- package/dist/components/SearchResult.js +0 -40
- package/dist/components/SearchResultThumbnail.d.ts +0 -38
- package/dist/components/SearchResultThumbnail.js +0 -38
- package/dist/components/SearchResults.d.ts +0 -39
- package/dist/components/SearchResults.js +0 -53
- package/dist/components/SearchRoot.d.ts +0 -44
- package/dist/components/SearchRoot.js +0 -132
- package/dist/context/SearchContext.d.ts +0 -55
- package/dist/context/SearchContext.js +0 -36
- package/dist/hooks/useSearch.d.ts +0 -56
- package/dist/hooks/useSearch.js +0 -116
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<a href="https://etoile.dev">
|
|
3
|
-
<img src="https://etoile.dev/assets/logo-black.svg" alt="
|
|
3
|
+
<img src="https://etoile.dev/assets/logo-black.svg" alt="Etoile" height="32" />
|
|
4
4
|
</a>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<h1 align="center">@etoile-dev/react</h1>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
|
-
<strong>Headless React primitives for search.</strong>
|
|
14
|
+
<strong>Headless React primitives for search — with Etoile-powered components and hooks.</strong>
|
|
15
15
|
<br />
|
|
16
16
|
Composable. Accessible. Zero styling.
|
|
17
17
|
</p>
|
|
@@ -24,19 +24,23 @@
|
|
|
24
24
|
|
|
25
25
|
## About
|
|
26
26
|
|
|
27
|
-
**@etoile-dev/react**
|
|
27
|
+
**@etoile-dev/react** is the React SDK for **Etoile**, and also a set of unstyled primitives you can wire to any data source.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
You get three layers:
|
|
30
|
+
|
|
31
|
+
- **Ready-to-use Etoile components** — `<Searchbar />` and `<SearchModal />`
|
|
32
|
+
- **Etoile data hooks** — `useEtoileSearch` (and `useSearch` alias)
|
|
33
|
+
- **Headless primitives** — `Searchbar.Root`, `Searchbar.Input`, `Searchbar.List`, `Searchbar.Item`, …
|
|
30
34
|
|
|
31
35
|
---
|
|
32
36
|
|
|
33
37
|
## Philosophy
|
|
34
38
|
|
|
35
39
|
- **Headless-first** — You control the appearance
|
|
36
|
-
- **Composable** — Build
|
|
37
|
-
- **Accessible** — Full ARIA
|
|
38
|
-
- **No magic** —
|
|
39
|
-
- **
|
|
40
|
+
- **Composable** — Build any search UX from small primitives
|
|
41
|
+
- **Accessible** — Full ARIA combobox / listbox pattern, keyboard navigation
|
|
42
|
+
- **No magic** — Predictable behavior, clear contracts
|
|
43
|
+
- **Zero style opinions** — Bring your own CSS (or use our optional theme)
|
|
40
44
|
|
|
41
45
|
---
|
|
42
46
|
|
|
@@ -50,330 +54,461 @@ npm i @etoile-dev/react
|
|
|
50
54
|
|
|
51
55
|
## Quickstart
|
|
52
56
|
|
|
57
|
+
The simplest possible usage — just an API key and a collection:
|
|
58
|
+
|
|
53
59
|
```tsx
|
|
54
|
-
import
|
|
60
|
+
import "@etoile-dev/react/styles.css";
|
|
61
|
+
import { Searchbar } from "@etoile-dev/react";
|
|
55
62
|
|
|
56
63
|
export default function App() {
|
|
57
|
-
return <
|
|
64
|
+
return <Searchbar apiKey="your-api-key" collections={["paintings"]} />;
|
|
58
65
|
}
|
|
59
66
|
```
|
|
60
67
|
|
|
68
|
+
Search multiple collections and handle selection:
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
<Searchbar
|
|
72
|
+
apiKey={process.env.ETOILE_API_KEY!}
|
|
73
|
+
collections={["paintings", "artists"]}
|
|
74
|
+
limit={10}
|
|
75
|
+
onSelect={(id) => router.push(`/work/${id}`)}
|
|
76
|
+
/>
|
|
77
|
+
```
|
|
78
|
+
|
|
61
79
|
---
|
|
62
80
|
|
|
63
|
-
##
|
|
81
|
+
## Headless primitives
|
|
64
82
|
|
|
65
|
-
|
|
83
|
+
Use `Searchbar.Root` and friends for full control with no Etoile dependency:
|
|
66
84
|
|
|
67
85
|
```tsx
|
|
68
|
-
import {
|
|
69
|
-
SearchRoot,
|
|
70
|
-
SearchInput,
|
|
71
|
-
SearchResults,
|
|
72
|
-
SearchResult,
|
|
73
|
-
} from "@etoile-dev/react";
|
|
86
|
+
import { Searchbar } from "@etoile-dev/react";
|
|
74
87
|
|
|
75
|
-
export default function
|
|
88
|
+
export default function LocalSearch() {
|
|
76
89
|
return (
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
<small>Score: {result.score.toFixed(2)}</small>
|
|
90
|
-
</SearchResult>
|
|
91
|
-
)}
|
|
92
|
-
</SearchResults>
|
|
93
|
-
</SearchRoot>
|
|
90
|
+
<Searchbar.Root onSelect={(id) => router.push(`/paintings/${id}`)}>
|
|
91
|
+
<Searchbar.Input placeholder="Search paintings…" />
|
|
92
|
+
<Searchbar.List>
|
|
93
|
+
{paintings.map((p) => (
|
|
94
|
+
<Searchbar.Item key={p.id} value={p.id} label={p.title}>
|
|
95
|
+
{p.title}
|
|
96
|
+
</Searchbar.Item>
|
|
97
|
+
))}
|
|
98
|
+
<Searchbar.Empty>No results.</Searchbar.Empty>
|
|
99
|
+
<Searchbar.Loading />
|
|
100
|
+
</Searchbar.List>
|
|
101
|
+
</Searchbar.Root>
|
|
94
102
|
);
|
|
95
103
|
}
|
|
96
104
|
```
|
|
97
105
|
|
|
98
|
-
|
|
106
|
+
Primitives are unstyled by default. To opt into the built-in theme while using
|
|
107
|
+
primitives, add `className="etoile-search"` on `Searchbar.Root` and import
|
|
108
|
+
`@etoile-dev/react/styles.css`.
|
|
99
109
|
|
|
100
|
-
|
|
110
|
+
---
|
|
101
111
|
|
|
102
|
-
|
|
112
|
+
## Custom rendering
|
|
103
113
|
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.result
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
+
```tsx
|
|
115
|
+
<Searchbar
|
|
116
|
+
apiKey={process.env.ETOILE_API_KEY!}
|
|
117
|
+
collections={["paintings", "artists"]}
|
|
118
|
+
onSelect={(id) => router.push(`/work/${id}`)}
|
|
119
|
+
renderItem={(result) => (
|
|
120
|
+
<Searchbar.Item value={result.external_id} label={result.title}>
|
|
121
|
+
<Searchbar.Thumbnail />
|
|
122
|
+
<div>
|
|
123
|
+
<strong>{result.title}</strong>
|
|
124
|
+
<span>{String(result.metadata?.artist ?? "")}</span>
|
|
125
|
+
</div>
|
|
126
|
+
</Searchbar.Item>
|
|
127
|
+
)}
|
|
128
|
+
/>
|
|
114
129
|
```
|
|
115
130
|
|
|
116
131
|
---
|
|
117
132
|
|
|
118
|
-
##
|
|
133
|
+
## Command palette / modal mode
|
|
119
134
|
|
|
120
|
-
|
|
135
|
+
Use the `<SearchModal />` convenience component for an Etoile-powered palette:
|
|
121
136
|
|
|
122
137
|
```tsx
|
|
123
138
|
import "@etoile-dev/react/styles.css";
|
|
124
|
-
import {
|
|
139
|
+
import { SearchModal } from "@etoile-dev/react";
|
|
125
140
|
|
|
126
|
-
<
|
|
141
|
+
<SearchModal apiKey="your-api-key" collections={["paintings"]} />;
|
|
127
142
|
```
|
|
128
143
|
|
|
129
|
-
|
|
144
|
+
Or compose the primitives yourself for full control:
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
<Searchbar.Root className="etoile-search">
|
|
148
|
+
<Searchbar.Trigger>
|
|
149
|
+
<Searchbar.Icon /> Search
|
|
150
|
+
</Searchbar.Trigger>
|
|
151
|
+
|
|
152
|
+
<Searchbar.Portal>
|
|
153
|
+
<Searchbar.Overlay className="overlay" />
|
|
154
|
+
<Searchbar.Content aria-label="Search paintings" className="palette">
|
|
155
|
+
<Searchbar.Input autoFocus placeholder="Search…" />
|
|
156
|
+
<Searchbar.List>
|
|
157
|
+
{results.map((r) => (
|
|
158
|
+
<Searchbar.Item key={r.id} value={r.id}>
|
|
159
|
+
{r.title}
|
|
160
|
+
</Searchbar.Item>
|
|
161
|
+
))}
|
|
162
|
+
<Searchbar.Empty>No results.</Searchbar.Empty>
|
|
163
|
+
</Searchbar.List>
|
|
164
|
+
</Searchbar.Content>
|
|
165
|
+
</Searchbar.Portal>
|
|
166
|
+
</Searchbar.Root>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Styling
|
|
172
|
+
|
|
173
|
+
### Data attributes
|
|
174
|
+
|
|
175
|
+
All primitives emit `data-*` attributes — no class coupling required:
|
|
130
176
|
|
|
131
|
-
|
|
177
|
+
```css
|
|
178
|
+
[role="option"][data-selected="true"] {
|
|
179
|
+
background: #f0f9ff;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
[role="option"][data-disabled="true"] {
|
|
183
|
+
opacity: 0.4;
|
|
184
|
+
cursor: not-allowed;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
[data-state="open"] {
|
|
188
|
+
border-color: #3b82f6;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
132
191
|
|
|
133
|
-
|
|
192
|
+
### Dark mode
|
|
134
193
|
|
|
135
194
|
```tsx
|
|
136
|
-
<
|
|
195
|
+
<Searchbar className="dark" apiKey="your-api-key" collections={["paintings"]} />
|
|
196
|
+
```
|
|
137
197
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
198
|
+
Or wrap a parent element:
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
<div className="dark">
|
|
202
|
+
<Searchbar apiKey="your-api-key" collections={["paintings"]} />
|
|
203
|
+
</div>
|
|
142
204
|
```
|
|
143
205
|
|
|
144
|
-
### CSS
|
|
206
|
+
### CSS variables
|
|
145
207
|
|
|
146
|
-
Every value is customizable
|
|
208
|
+
Every value is customizable:
|
|
147
209
|
|
|
148
210
|
```css
|
|
149
211
|
.etoile-search {
|
|
150
|
-
/* Colors */
|
|
151
212
|
--etoile-bg: #ffffff;
|
|
152
213
|
--etoile-border: #e4e4e7;
|
|
153
214
|
--etoile-text: #09090b;
|
|
154
215
|
--etoile-text-muted: #71717a;
|
|
155
216
|
--etoile-selected: #f4f4f5;
|
|
156
|
-
--etoile-ring: #18181b;
|
|
157
|
-
|
|
158
|
-
/* Sizing */
|
|
159
217
|
--etoile-radius: 12px;
|
|
160
218
|
--etoile-input-height: 44px;
|
|
161
|
-
|
|
162
|
-
|
|
219
|
+
}
|
|
220
|
+
```
|
|
163
221
|
|
|
164
|
-
|
|
165
|
-
--etoile-input-padding-x: 16px;
|
|
166
|
-
--etoile-result-gap: 16px;
|
|
167
|
-
--etoile-results-offset: 8px;
|
|
222
|
+
See `styles.css` for all 40+ variables.
|
|
168
223
|
|
|
169
|
-
|
|
170
|
-
--etoile-font-size-input: 15px;
|
|
171
|
-
--etoile-font-size-title: 14px;
|
|
224
|
+
---
|
|
172
225
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
226
|
+
## API
|
|
227
|
+
|
|
228
|
+
### `<Searchbar />`
|
|
229
|
+
|
|
230
|
+
Ready-to-use search component powered by Etoile.
|
|
231
|
+
|
|
232
|
+
| Prop | Type | Default |
|
|
233
|
+
| ------------- | ------------------------------------- | ----------------- |
|
|
234
|
+
| `apiKey` | `string` | **required** |
|
|
235
|
+
| `collections` | `string[]` | **required** |
|
|
236
|
+
| `limit` | `number` | `10` |
|
|
237
|
+
| `offset` | `number` | `0` (API default) |
|
|
238
|
+
| `debounceMs` | `number` | `100` |
|
|
239
|
+
| `placeholder` | `string` | `"Search…"` |
|
|
240
|
+
| `filters` | `SearchFilter[]` | |
|
|
241
|
+
| `autoFilters` | `boolean` | |
|
|
242
|
+
| `renderItem` | `(result: SearchResult) => ReactNode` | |
|
|
243
|
+
| `onSelect` | `(value: string) => void` | |
|
|
244
|
+
| `hotkey` | `string` | |
|
|
245
|
+
| `className` | `string` | `"etoile-search"` |
|
|
177
246
|
|
|
178
|
-
|
|
247
|
+
Also supports non-state DOM/behavior props from `Searchbar.Root`.
|
|
179
248
|
|
|
180
249
|
---
|
|
181
250
|
|
|
182
|
-
|
|
251
|
+
### `<SearchModal />`
|
|
252
|
+
|
|
253
|
+
Ready-to-use command palette powered by Etoile.
|
|
254
|
+
|
|
255
|
+
| Prop | Type | Default |
|
|
256
|
+
| ------------- | ------------------------------------- | ----------------- |
|
|
257
|
+
| `apiKey` | `string` | **required** |
|
|
258
|
+
| `collections` | `string[]` | **required** |
|
|
259
|
+
| `limit` | `number` | `10` |
|
|
260
|
+
| `offset` | `number` | `0` (API default) |
|
|
261
|
+
| `debounceMs` | `number` | `100` |
|
|
262
|
+
| `placeholder` | `string` | `"Search…"` |
|
|
263
|
+
| `filters` | `SearchFilter[]` | |
|
|
264
|
+
| `autoFilters` | `boolean` | |
|
|
265
|
+
| `hotkey` | `string` | `"mod+k"` |
|
|
266
|
+
| `modalLabel` | `string` | `"Search"` |
|
|
267
|
+
| `renderItem` | `(result: SearchResult) => ReactNode` | |
|
|
268
|
+
| `onSelect` | `(value: string) => void` | |
|
|
269
|
+
| `className` | `string` | `"etoile-search"` |
|
|
183
270
|
|
|
184
|
-
|
|
271
|
+
---
|
|
185
272
|
|
|
186
|
-
|
|
187
|
-
|
|
273
|
+
### `<Searchbar.Root />`
|
|
274
|
+
|
|
275
|
+
Context provider and state machine for headless usage.
|
|
276
|
+
|
|
277
|
+
| Prop | Type | Default |
|
|
278
|
+
| ---------------- | --------------------------------- | ---------- |
|
|
279
|
+
| `open` | `boolean` | |
|
|
280
|
+
| `defaultOpen` | `boolean` | `false` |
|
|
281
|
+
| `onOpenChange` | `(open: boolean) => void` | |
|
|
282
|
+
| `search` | `string` | |
|
|
283
|
+
| `defaultSearch` | `string` | `""` |
|
|
284
|
+
| `onSearchChange` | `(search: string) => void` | |
|
|
285
|
+
| `value` | `string \| null` | |
|
|
286
|
+
| `defaultValue` | `string \| null` | `null` |
|
|
287
|
+
| `onValueChange` | `(value: string \| null) => void` | |
|
|
288
|
+
| `isLoading` | `boolean` | `false` |
|
|
289
|
+
| `error` | `unknown` | |
|
|
290
|
+
| `hotkey` | `string` | |
|
|
291
|
+
| `hotkeyBehavior` | `"focus" \| "toggle"` | `"toggle"` |
|
|
292
|
+
| `onSelect` | `(value: string) => void` | |
|
|
293
|
+
| `className` | `string` | |
|
|
294
|
+
| `asChild` | `boolean` | `false` |
|
|
188
295
|
|
|
189
|
-
|
|
190
|
-
const { query, setQuery, results, isLoading } = useSearch({
|
|
191
|
-
apiKey: "your-api-key",
|
|
192
|
-
collections: ["paintings"],
|
|
193
|
-
});
|
|
296
|
+
**Keyboard shortcuts:**
|
|
194
297
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
value={query}
|
|
199
|
-
onChange={(e) => setQuery(e.target.value)}
|
|
200
|
-
placeholder="Search paintings..."
|
|
201
|
-
/>
|
|
202
|
-
{isLoading && <p>Loading...</p>}
|
|
203
|
-
<ul>
|
|
204
|
-
{results.map((result) => (
|
|
205
|
-
<li key={result.external_id}>{result.title}</li>
|
|
206
|
-
))}
|
|
207
|
-
</ul>
|
|
208
|
-
</div>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
```
|
|
298
|
+
- `↑` / `↓` — Navigate items
|
|
299
|
+
- `Enter` — Select active item
|
|
300
|
+
- `Escape` — Close list
|
|
212
301
|
|
|
213
302
|
---
|
|
214
303
|
|
|
215
|
-
|
|
304
|
+
### `<Searchbar.Modal />`
|
|
305
|
+
|
|
306
|
+
Headless modal primitive (`Root + Portal + Overlay + Content`).
|
|
216
307
|
|
|
217
|
-
|
|
308
|
+
| Prop | Type | Default |
|
|
309
|
+
| ------------ | -------- | ---------- |
|
|
310
|
+
| `aria-label` | `string` | `"Search"` |
|
|
311
|
+
|
|
312
|
+
Also accepts `Searchbar.Root` props.
|
|
313
|
+
|
|
314
|
+
---
|
|
218
315
|
|
|
219
|
-
|
|
316
|
+
### `<Searchbar.Input />`
|
|
220
317
|
|
|
221
|
-
| Prop
|
|
222
|
-
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
225
|
-
| `limit` | `number` | | `10` |
|
|
226
|
-
| `debounceMs` | `number` | | `100` |
|
|
227
|
-
| `renderResult` | `(result: SearchResultData) => React.ReactNode` | | |
|
|
318
|
+
| Prop | Type | Default |
|
|
319
|
+
| ------------- | --------- | ------- |
|
|
320
|
+
| `placeholder` | `string` | |
|
|
321
|
+
| `asChild` | `boolean` | `false` |
|
|
228
322
|
|
|
229
323
|
---
|
|
230
324
|
|
|
231
|
-
### `<
|
|
325
|
+
### `<Searchbar.ModalInput />`
|
|
232
326
|
|
|
233
|
-
|
|
327
|
+
Pre-composed modal input row.
|
|
234
328
|
|
|
235
|
-
| Prop | Type
|
|
236
|
-
|
|
237
|
-
| `
|
|
238
|
-
| `
|
|
239
|
-
| `
|
|
240
|
-
| `debounceMs` | `number` | | `100` |
|
|
241
|
-
| `autoFocus` | `boolean` | | `false` |
|
|
242
|
-
| `children` | `React.ReactNode` | ✓ | |
|
|
329
|
+
| Prop | Type | Default |
|
|
330
|
+
| ------------- | ------------------- | ----------- |
|
|
331
|
+
| `placeholder` | `string` | `"Search…"` |
|
|
332
|
+
| `icon` | `ReactNode \| null` | `<Icon />` |
|
|
333
|
+
| `kbd` | `ReactNode \| null` | `"Esc"` |
|
|
243
334
|
|
|
244
335
|
---
|
|
245
336
|
|
|
246
|
-
### `<
|
|
337
|
+
### `<Searchbar.List />`
|
|
247
338
|
|
|
248
|
-
|
|
339
|
+
Container with `role="listbox"`. Renders when open. In modal mode, it hides when
|
|
340
|
+
query is empty.
|
|
249
341
|
|
|
250
|
-
| Prop
|
|
251
|
-
|
|
252
|
-
| `
|
|
253
|
-
| `className` | `string` |
|
|
342
|
+
| Prop | Type | Default |
|
|
343
|
+
| --------- | --------- | ------- |
|
|
344
|
+
| `asChild` | `boolean` | `false` |
|
|
254
345
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
### `<Searchbar.Item />`
|
|
349
|
+
|
|
350
|
+
| Prop | Type | Default |
|
|
351
|
+
| ---------- | ------------------------- | -------- |
|
|
352
|
+
| `value` | `string` | required |
|
|
353
|
+
| `label` | `string` | `value` |
|
|
354
|
+
| `disabled` | `boolean` | `false` |
|
|
355
|
+
| `onSelect` | `(value: string) => void` | |
|
|
356
|
+
| `asChild` | `boolean` | `false` |
|
|
357
|
+
|
|
358
|
+
**Data attributes:** `data-selected`, `data-disabled`, `data-value`
|
|
259
359
|
|
|
260
360
|
---
|
|
261
361
|
|
|
262
|
-
### `<
|
|
362
|
+
### `<Searchbar.Group />`
|
|
363
|
+
|
|
364
|
+
| Prop | Type |
|
|
365
|
+
| ------- | -------- |
|
|
366
|
+
| `label` | `string` |
|
|
263
367
|
|
|
264
|
-
|
|
368
|
+
---
|
|
265
369
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
| `children` | `(result: SearchResultData) => React.ReactNode` | ✓ |
|
|
370
|
+
### `<Searchbar.Separator />`
|
|
371
|
+
|
|
372
|
+
Visual separator (`role="separator"`).
|
|
270
373
|
|
|
271
374
|
---
|
|
272
375
|
|
|
273
|
-
### `<
|
|
376
|
+
### `<Searchbar.Empty />`
|
|
377
|
+
|
|
378
|
+
Renders when: list is open, query is non-empty, no items match, and not loading.
|
|
274
379
|
|
|
275
|
-
|
|
380
|
+
---
|
|
276
381
|
|
|
277
|
-
|
|
278
|
-
|-------------|-------------------|----------|
|
|
279
|
-
| `className` | `string` | |
|
|
280
|
-
| `children` | `React.ReactNode` | ✓ |
|
|
382
|
+
### `<Searchbar.Loading />`
|
|
281
383
|
|
|
282
|
-
|
|
283
|
-
- `data-selected="true" | "false"` — Active state
|
|
284
|
-
- `data-index="number"` — Result position
|
|
384
|
+
Renders when `isLoading={true}` is passed to `Searchbar.Root`.
|
|
285
385
|
|
|
286
386
|
---
|
|
287
387
|
|
|
288
|
-
### `<
|
|
388
|
+
### `<Searchbar.Error />`
|
|
289
389
|
|
|
290
|
-
|
|
390
|
+
Renders when `error` is set on `Searchbar.Root`. Accepts a render function:
|
|
291
391
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
| `alt` | `string` | | `result.title` |
|
|
296
|
-
| `size` | `number` | | `40` |
|
|
297
|
-
| `className` | `string` | | |
|
|
392
|
+
```tsx
|
|
393
|
+
<Searchbar.Error>{(err) => `Search failed: ${String(err)}`}</Searchbar.Error>
|
|
394
|
+
```
|
|
298
395
|
|
|
299
396
|
---
|
|
300
397
|
|
|
301
|
-
### `<
|
|
398
|
+
### `<Searchbar.Portal />`
|
|
399
|
+
|
|
400
|
+
Portals children to `document.body` (or a custom `container`).
|
|
302
401
|
|
|
303
|
-
|
|
402
|
+
---
|
|
304
403
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
| `className` | `string` | | |
|
|
404
|
+
### `<Searchbar.Overlay />`
|
|
405
|
+
|
|
406
|
+
Backdrop for modal/palette mode. Renders when open.
|
|
309
407
|
|
|
310
408
|
---
|
|
311
409
|
|
|
312
|
-
### `<
|
|
410
|
+
### `<Searchbar.Content />`
|
|
313
411
|
|
|
314
|
-
|
|
412
|
+
Dialog panel for modal/palette mode. Focuses first focusable child on open.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
### `<Searchbar.Trigger />`
|
|
315
417
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
418
|
+
Button that toggles open state.
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
### `<Searchbar.Icon />`
|
|
423
|
+
|
|
424
|
+
Search magnifying glass SVG icon.
|
|
425
|
+
|
|
426
|
+
| Prop | Type | Default |
|
|
427
|
+
| ------ | -------- | ------- |
|
|
428
|
+
| `size` | `number` | `18` |
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
### `<Searchbar.Kbd />`
|
|
433
|
+
|
|
434
|
+
Keyboard shortcut badge.
|
|
320
435
|
|
|
321
436
|
```tsx
|
|
322
|
-
<
|
|
323
|
-
<
|
|
437
|
+
<Searchbar.Kbd /> // "⌘K"
|
|
438
|
+
<Searchbar.Kbd>/</Searchbar.Kbd>
|
|
324
439
|
```
|
|
325
440
|
|
|
326
441
|
---
|
|
327
442
|
|
|
328
|
-
###
|
|
443
|
+
### `<Searchbar.Thumbnail />`
|
|
444
|
+
|
|
445
|
+
Thumbnail image. Auto-reads `metadata.thumbnailUrl` from item context when
|
|
446
|
+
used inside the Etoile `<Searchbar />` wrapper.
|
|
447
|
+
|
|
448
|
+
| Prop | Type | Default |
|
|
449
|
+
| ------ | -------- | ------------ |
|
|
450
|
+
| `src` | `string` | from context |
|
|
451
|
+
| `alt` | `string` | item title |
|
|
452
|
+
| `size` | `number` | `40` |
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
### `useSearchbarContext()`
|
|
329
457
|
|
|
330
|
-
|
|
458
|
+
Access store helpers from any component inside `Searchbar.Root`.
|
|
331
459
|
|
|
332
|
-
|
|
460
|
+
```tsx
|
|
461
|
+
import { useSearchbarContext, useSearchbarStore } from "@etoile-dev/react";
|
|
333
462
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
463
|
+
function QueryDisplay() {
|
|
464
|
+
const { store } = useSearchbarContext();
|
|
465
|
+
const query = useSearchbarStore(store, (s) => s.query);
|
|
466
|
+
return <span>{query}</span>;
|
|
467
|
+
}
|
|
468
|
+
```
|
|
340
469
|
|
|
341
|
-
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
### `useEtoileSearch(options)`
|
|
342
473
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
474
|
+
Headless data hook for live Etoile search.
|
|
475
|
+
|
|
476
|
+
```tsx
|
|
477
|
+
import { useEtoileSearch } from "@etoile-dev/react";
|
|
478
|
+
|
|
479
|
+
const [query, setQuery] = useState("");
|
|
480
|
+
const { results, isLoading } = useEtoileSearch({
|
|
481
|
+
apiKey: process.env.ETOILE_API_KEY!,
|
|
482
|
+
collections: ["paintings"],
|
|
483
|
+
query,
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
Returns `results`, `isLoading`, `error`, `appliedFilters`, and `refinedQuery`.
|
|
352
488
|
|
|
353
489
|
---
|
|
354
490
|
|
|
355
491
|
## Types
|
|
356
492
|
|
|
357
493
|
```ts
|
|
358
|
-
type
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
content?: string;
|
|
364
|
-
metadata: Record<string, unknown>;
|
|
365
|
-
};
|
|
494
|
+
import type {
|
|
495
|
+
SearchResult,
|
|
496
|
+
SearchFilter,
|
|
497
|
+
FilterOperator,
|
|
498
|
+
} from "@etoile-dev/react";
|
|
366
499
|
```
|
|
367
500
|
|
|
501
|
+
`SearchResultData` remains exported as a deprecated alias.
|
|
502
|
+
|
|
368
503
|
---
|
|
369
504
|
|
|
370
505
|
## Why @etoile-dev/react?
|
|
371
506
|
|
|
372
|
-
- **
|
|
373
|
-
- **
|
|
374
|
-
- **
|
|
375
|
-
- **
|
|
376
|
-
- **
|
|
507
|
+
- **Fast by default** — only the components that depend on the query re-render, not the whole tree
|
|
508
|
+
- **Stable selection** — items are identified by value, not by index
|
|
509
|
+
- **Controlled or uncontrolled** — every stateful prop supports both patterns
|
|
510
|
+
- **Composable** — render any element as any primitive with `asChild`
|
|
511
|
+
- **No opinions** — primitives emit `data-*` attributes and inject no theme classes
|
|
377
512
|
|
|
378
513
|
---
|
|
379
514
|
|