@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.
Files changed (74) hide show
  1. package/README.md +341 -206
  2. package/dist/Searchbar.d.ts +315 -0
  3. package/dist/Searchbar.js +207 -0
  4. package/dist/context.d.ts +57 -0
  5. package/dist/context.js +32 -0
  6. package/dist/hooks/useEtoileSearch.d.ts +122 -0
  7. package/dist/hooks/useEtoileSearch.js +138 -0
  8. package/dist/index.d.ts +44 -19
  9. package/dist/index.js +37 -12
  10. package/dist/primitives/Content.d.ts +34 -0
  11. package/dist/primitives/Content.js +108 -0
  12. package/dist/primitives/Empty.d.ts +25 -0
  13. package/dist/primitives/Empty.js +25 -0
  14. package/dist/primitives/Error.d.ts +29 -0
  15. package/dist/primitives/Error.js +26 -0
  16. package/dist/primitives/Group.d.ts +30 -0
  17. package/dist/primitives/Group.js +22 -0
  18. package/dist/primitives/Icon.d.ts +21 -0
  19. package/dist/primitives/Icon.js +14 -0
  20. package/dist/primitives/Input.d.ts +32 -0
  21. package/dist/primitives/Input.js +70 -0
  22. package/dist/primitives/Item.d.ts +61 -0
  23. package/dist/primitives/Item.js +76 -0
  24. package/dist/primitives/Kbd.d.ts +20 -0
  25. package/dist/primitives/Kbd.js +13 -0
  26. package/dist/primitives/List.d.ts +35 -0
  27. package/dist/primitives/List.js +37 -0
  28. package/dist/primitives/Loading.d.ts +25 -0
  29. package/dist/primitives/Loading.js +26 -0
  30. package/dist/primitives/Modal.d.ts +39 -0
  31. package/dist/primitives/Modal.js +37 -0
  32. package/dist/primitives/ModalInput.d.ts +61 -0
  33. package/dist/primitives/ModalInput.js +33 -0
  34. package/dist/primitives/Overlay.d.ts +21 -0
  35. package/dist/primitives/Overlay.js +41 -0
  36. package/dist/primitives/Portal.d.ts +28 -0
  37. package/dist/primitives/Portal.js +30 -0
  38. package/dist/primitives/Root.d.ts +116 -0
  39. package/dist/primitives/Root.js +413 -0
  40. package/dist/primitives/Separator.d.ts +19 -0
  41. package/dist/primitives/Separator.js +18 -0
  42. package/dist/primitives/Thumbnail.d.ts +31 -0
  43. package/dist/primitives/Thumbnail.js +59 -0
  44. package/dist/primitives/Trigger.d.ts +28 -0
  45. package/dist/primitives/Trigger.js +35 -0
  46. package/dist/store.d.ts +38 -0
  47. package/dist/store.js +63 -0
  48. package/dist/styles.css +480 -133
  49. package/dist/types.d.ts +3 -31
  50. package/dist/utils/composeRefs.d.ts +12 -0
  51. package/dist/utils/composeRefs.js +27 -0
  52. package/dist/utils/slot.d.ts +22 -0
  53. package/dist/utils/slot.js +58 -0
  54. package/package.json +9 -5
  55. package/dist/Search.d.ts +0 -39
  56. package/dist/Search.js +0 -31
  57. package/dist/components/SearchIcon.d.ts +0 -22
  58. package/dist/components/SearchIcon.js +0 -17
  59. package/dist/components/SearchInput.d.ts +0 -30
  60. package/dist/components/SearchInput.js +0 -59
  61. package/dist/components/SearchKbd.d.ts +0 -30
  62. package/dist/components/SearchKbd.js +0 -24
  63. package/dist/components/SearchResult.d.ts +0 -31
  64. package/dist/components/SearchResult.js +0 -40
  65. package/dist/components/SearchResultThumbnail.d.ts +0 -38
  66. package/dist/components/SearchResultThumbnail.js +0 -38
  67. package/dist/components/SearchResults.d.ts +0 -39
  68. package/dist/components/SearchResults.js +0 -53
  69. package/dist/components/SearchRoot.d.ts +0 -44
  70. package/dist/components/SearchRoot.js +0 -132
  71. package/dist/context/SearchContext.d.ts +0 -55
  72. package/dist/context/SearchContext.js +0 -36
  73. package/dist/hooks/useSearch.d.ts +0 -56
  74. package/dist/hooks/useSearch.js +0 -116
package/dist/styles.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Étoile React - Default Theme
2
+ * Etoile React Default Theme
3
3
  * A minimal, elegant theme.
4
4
  *
5
5
  * Usage:
@@ -14,6 +14,11 @@
14
14
  ============================================ */
15
15
 
16
16
  .etoile-search {
17
+ /*
18
+ * Public theme tokens (stable API):
19
+ * Consumers can safely override these in app-level CSS.
20
+ */
21
+
17
22
  /* Colors */
18
23
  --etoile-bg: #ffffff;
19
24
  --etoile-bg-subtle: #fafafa;
@@ -32,9 +37,11 @@
32
37
  --etoile-radius-sm: 8px;
33
38
  --etoile-radius-kbd: 6px;
34
39
 
35
- /* Shadows */
36
- --etoile-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
37
- --etoile-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
40
+ /* Shadows (Tailwind shadow-xl inspired) */
41
+ --etoile-shadow:
42
+ 0 4px 6px -1px rgb(0 0 0 / 0.04), 0 2px 4px -2px rgb(0 0 0 / 0.03);
43
+ --etoile-shadow-lg:
44
+ 0 20px 25px -5px rgb(0 0 0 / 0.05), 0 8px 10px -6px rgb(0 0 0 / 0.05);
38
45
 
39
46
  /* Animation */
40
47
  --etoile-transition: 150ms cubic-bezier(0.4, 0, 0.2, 1);
@@ -42,17 +49,15 @@
42
49
  --etoile-spinner-duration: 600ms;
43
50
 
44
51
  /* Typography */
45
- --etoile-font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
46
- --etoile-font-size-input: 15px;
47
- --etoile-font-size-title: 14px;
48
- --etoile-font-size-subtitle: 13px;
49
- --etoile-font-size-small: 14px;
52
+ --etoile-font-family:
53
+ system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
54
+ --etoile-font-size: 15px;
50
55
  --etoile-font-size-kbd: 11px;
51
- --etoile-font-weight-input: 450;
52
- --etoile-font-weight-title: 500;
53
- --etoile-font-weight-subtitle: 500;
54
56
  --etoile-line-height: 1.3;
55
57
 
58
+ /* Inline searchbar max width */
59
+ --etoile-searchbar-max-width: 400px;
60
+
56
61
  /* Input */
57
62
  --etoile-input-height: 44px;
58
63
  --etoile-input-padding-x: 16px;
@@ -62,6 +67,7 @@
62
67
  --etoile-results-offset: 8px;
63
68
  --etoile-results-padding: 6px;
64
69
  --etoile-results-gap: 4px;
70
+ --etoile-scroll-padding: 8px;
65
71
  --etoile-results-max-height: 300px;
66
72
  --etoile-results-z-index: 50;
67
73
 
@@ -70,6 +76,8 @@
70
76
  --etoile-result-padding-y: 10px;
71
77
  --etoile-result-gap: 16px;
72
78
  --etoile-result-content-gap: 1px;
79
+ --etoile-group-stack-gap: 6px;
80
+ --etoile-group-label-padding: 6px 10px 2px;
73
81
 
74
82
  /* Thumbnail */
75
83
  --etoile-thumbnail-size: 40px;
@@ -81,27 +89,72 @@
81
89
 
82
90
  /* Keyboard Badge */
83
91
  --etoile-kbd-height: 20px;
92
+ --etoile-kbd-min-width: 20px;
84
93
  --etoile-kbd-padding-x: 4px;
85
94
  --etoile-kbd-offset: 10px;
86
95
  --etoile-input-padding-with-kbd: 48px;
87
96
 
88
97
  /* Spinner */
89
- --etoile-spinner-size: 16px;
98
+ --etoile-spinner-size: 20px;
90
99
  --etoile-spinner-border-width: 2px;
91
100
 
92
101
  /* Focus Ring */
93
102
  --etoile-ring-width: 3px;
94
103
  --etoile-ring-opacity: 0.1;
95
104
 
96
- /* Empty/Loading States */
105
+ /* Empty / Loading states */
97
106
  --etoile-empty-padding-x: 16px;
98
107
  --etoile-empty-padding-y: 24px;
99
108
  --etoile-loading-padding: 16px;
100
109
  --etoile-loading-gap: 8px;
101
110
 
102
- /* Base Styles */
111
+ /* Modal */
112
+ --etoile-overlay-bg: rgb(0 0 0 / 0.25);
113
+ --etoile-content-width: min(560px, calc(100vw - 32px));
114
+ --etoile-content-max-height: min(70vh, 640px);
115
+ --etoile-content-padding: 12px;
116
+ --etoile-content-z-index: 1000;
117
+ --etoile-content-top: 14vh;
118
+ --etoile-overlay-duration: 300ms;
119
+ --etoile-content-duration: 300ms;
120
+ --etoile-content-height-duration: 220ms;
121
+ --etoile-content-enter-scale: 0.96;
122
+ /* Max-height of the results list inside the modal */
123
+ --etoile-content-list-max-height: 400px;
124
+ /* Offset used by the list panel inside content */
125
+ --etoile-content-list-offset: var(--etoile-content-padding);
126
+ /* Modal border colors */
127
+ --etoile-content-border-color: rgb(0 0 0 / 0.04);
128
+ --etoile-content-border-color-dark: rgb(255 255 255 / 0.06);
129
+ --etoile-content-list-border-color: var(--etoile-border);
130
+ --etoile-content-list-border-color-dark: rgb(255 255 255 / 0.08);
131
+ --etoile-overlay-bg-dark: rgb(0 0 0 / 0.4);
132
+ --etoile-content-bg-dark: #111111;
133
+ --etoile-modal-input-font-size: 16px;
134
+ --etoile-modal-input-font-weight: 500;
135
+ /* Modal input row padding */
136
+ --etoile-modal-input-padding-x: var(--etoile-content-padding);
137
+ --etoile-modal-input-padding-y: var(--etoile-modal-input-padding-x);
138
+
139
+ /*
140
+ * Internal aliases (implementation details):
141
+ * Keep these private so we can refactor styles without changing
142
+ * the public token surface.
143
+ */
144
+ --_etoile-focus-ring-light: rgb(24 24 27 / var(--etoile-ring-opacity));
145
+ --_etoile-focus-ring-dark: rgb(212 212 216 / var(--etoile-ring-opacity));
146
+
147
+ /* Base */
103
148
  position: relative;
104
149
  font-family: var(--etoile-font-family);
150
+ font-size: var(--etoile-font-size);
151
+ }
152
+
153
+ /* Inline searchbar only — cap width; exclude portal elements */
154
+ .etoile-search:not([data-slot="searchbar-content"]):not(
155
+ [data-slot="searchbar-overlay"]
156
+ ) {
157
+ max-width: var(--etoile-searchbar-max-width);
105
158
  }
106
159
 
107
160
  /* ============================================
@@ -125,85 +178,27 @@
125
178
  }
126
179
 
127
180
  /* ============================================
128
- Search Input
181
+ Shared — Keyboard Shortcut Badge
129
182
  ============================================ */
130
183
 
131
- .etoile-search input[role="combobox"] {
132
- width: 100%;
133
- height: var(--etoile-input-height);
134
- padding: 0 var(--etoile-input-padding-x);
135
- background: var(--etoile-bg);
184
+ .etoile-search [data-slot="searchbar-kbd"] {
185
+ display: inline-flex;
186
+ align-items: center;
187
+ justify-content: center;
188
+ min-width: var(--etoile-kbd-min-width);
189
+ height: var(--etoile-kbd-height);
190
+ padding: 0 var(--etoile-kbd-padding-x);
191
+ background: var(--etoile-bg-subtle);
136
192
  border: var(--etoile-input-border-width) solid var(--etoile-border);
137
- border-radius: var(--etoile-radius);
138
- color: var(--etoile-text);
139
- font-size: var(--etoile-font-size-input);
140
- font-weight: var(--etoile-font-weight-input);
141
- outline: none;
142
- transition: all var(--etoile-transition);
143
- }
144
-
145
- .etoile-search input[role="combobox"]::placeholder {
193
+ border-radius: var(--etoile-radius-kbd);
146
194
  color: var(--etoile-text-placeholder);
147
- }
148
-
149
- .etoile-search input[role="combobox"]:hover {
150
- border-color: var(--etoile-border-hover);
151
- }
152
-
153
- .etoile-search input[role="combobox"]:focus {
154
- border-color: var(--etoile-ring);
155
- box-shadow: 0 0 0 var(--etoile-ring-width) rgb(24 24 27 / var(--etoile-ring-opacity));
156
- }
157
-
158
- .dark .etoile-search input[role="combobox"]:focus,
159
- .etoile-search.dark input[role="combobox"]:focus {
160
- box-shadow: 0 0 0 var(--etoile-ring-width) rgb(212 212 216 / var(--etoile-ring-opacity));
161
- }
162
-
163
- /* ============================================
164
- Search Results Container
165
- ============================================ */
166
-
167
- .etoile-search [role="listbox"] {
168
- position: absolute;
169
- display: flex;
170
- flex-direction: column;
171
- gap: var(--etoile-results-gap);
172
- top: calc(100% + var(--etoile-results-offset));
173
- left: 0;
174
- right: 0;
175
- z-index: var(--etoile-results-z-index);
176
- max-height: var(--etoile-results-max-height);
177
- overflow-y: auto;
178
- padding: var(--etoile-results-padding);
179
- background: var(--etoile-bg);
180
- border: var(--etoile-input-border-width) solid var(--etoile-border);
181
- border-radius: var(--etoile-radius);
182
- box-shadow: var(--etoile-shadow-lg);
183
- animation: etoile-fade-in var(--etoile-animation-duration) ease-out;
184
-
185
- /* Hide scrollbar */
186
- scrollbar-width: none;
187
- -ms-overflow-style: none;
188
- }
189
-
190
- .etoile-search [role="listbox"]::-webkit-scrollbar {
191
- display: none;
192
- }
193
-
194
- @keyframes etoile-fade-in {
195
- from {
196
- opacity: 0;
197
- transform: translateY(-4px) scale(0.98);
198
- }
199
- to {
200
- opacity: 1;
201
- transform: translateY(0) scale(1);
202
- }
195
+ font-size: var(--etoile-font-size-kbd);
196
+ font-family: inherit;
197
+ pointer-events: none;
203
198
  }
204
199
 
205
200
  /* ============================================
206
- Search Result Item
201
+ Shared — Search Result Item
207
202
  ============================================ */
208
203
 
209
204
  .etoile-search [role="option"] {
@@ -211,6 +206,7 @@
211
206
  align-items: center;
212
207
  gap: var(--etoile-result-gap);
213
208
  padding: var(--etoile-result-padding-y) var(--etoile-result-padding-x);
209
+ font-size: var(--etoile-font-size);
214
210
  border-radius: var(--etoile-radius-sm);
215
211
  cursor: pointer;
216
212
  outline: none;
@@ -227,7 +223,30 @@
227
223
  }
228
224
 
229
225
  /* ============================================
230
- Search Result Thumbnail
226
+ Shared Group
227
+ ============================================ */
228
+
229
+ .etoile-search [data-slot="searchbar-group"] {
230
+ display: flex;
231
+ flex-direction: column;
232
+ gap: var(--etoile-results-gap);
233
+ }
234
+
235
+ .etoile-search [data-slot="searchbar-group"] + [data-slot="searchbar-group"] {
236
+ margin-top: var(--etoile-group-stack-gap);
237
+ }
238
+
239
+ .etoile-search [data-slot="searchbar-group-label"] {
240
+ padding: var(--etoile-group-label-padding);
241
+ font-size: 12px;
242
+ font-weight: 600;
243
+ color: var(--etoile-text-muted);
244
+ letter-spacing: 0.01em;
245
+ text-transform: capitalize;
246
+ }
247
+
248
+ /* ============================================
249
+ Shared — Thumbnail
231
250
  ============================================ */
232
251
 
233
252
  .etoile-search [role="option"] img {
@@ -240,10 +259,10 @@
240
259
  }
241
260
 
242
261
  /* ============================================
243
- Search Result Content
262
+ Shared Result Content
244
263
  ============================================ */
245
264
 
246
- .etoile-search .etoile-result-content {
265
+ .etoile-search [data-slot="searchbar-result-content"] {
247
266
  display: flex;
248
267
  flex-direction: column;
249
268
  gap: var(--etoile-result-content-gap);
@@ -252,9 +271,7 @@
252
271
  text-align: left;
253
272
  }
254
273
 
255
- .etoile-search .etoile-result-title {
256
- font-size: var(--etoile-font-size-title);
257
- font-weight: var(--etoile-font-weight-title);
274
+ .etoile-search [data-slot="searchbar-result-title"] {
258
275
  color: var(--etoile-text);
259
276
  line-height: var(--etoile-line-height);
260
277
  overflow: hidden;
@@ -262,9 +279,7 @@
262
279
  white-space: nowrap;
263
280
  }
264
281
 
265
- .etoile-search .etoile-result-subtitle {
266
- font-size: var(--etoile-font-size-subtitle);
267
- font-weight: var(--etoile-font-weight-subtitle);
282
+ .etoile-search [data-slot="searchbar-result-subtitle"] {
268
283
  color: var(--etoile-text-muted);
269
284
  line-height: var(--etoile-line-height);
270
285
  overflow: hidden;
@@ -274,37 +289,38 @@
274
289
  }
275
290
 
276
291
  /* ============================================
277
- Empty State
292
+ Shared — Empty State
278
293
  ============================================ */
279
294
 
280
- .etoile-search .etoile-empty {
295
+ .etoile-search [data-slot="searchbar-empty"] {
281
296
  padding: var(--etoile-empty-padding-y) var(--etoile-empty-padding-x);
282
297
  text-align: center;
283
298
  color: var(--etoile-text-muted);
284
- font-size: var(--etoile-font-size-small);
299
+ }
300
+
301
+ .etoile-search [data-slot="searchbar-empty-query"] {
302
+ color: var(--etoile-text);
285
303
  }
286
304
 
287
305
  /* ============================================
288
- Loading State
306
+ Shared — Loading State
289
307
  ============================================ */
290
308
 
291
- .etoile-search .etoile-loading {
309
+ .etoile-search [data-slot="searchbar-loading"] {
292
310
  display: flex;
293
311
  align-items: center;
294
312
  justify-content: center;
295
313
  padding: var(--etoile-loading-padding);
296
- color: var(--etoile-text-muted);
297
- font-size: var(--etoile-font-size-small);
314
+ color: var(--etoile-text-placeholder);
298
315
  gap: var(--etoile-loading-gap);
299
316
  }
300
317
 
301
- .etoile-search .etoile-spinner {
318
+ .etoile-search [data-slot="searchbar-spinner"] {
302
319
  width: var(--etoile-spinner-size);
303
320
  height: var(--etoile-spinner-size);
304
- border: var(--etoile-spinner-border-width) solid var(--etoile-border);
305
- border-top-color: var(--etoile-text-muted);
306
- border-radius: 50%;
321
+ color: var(--etoile-text-placeholder);
307
322
  animation: etoile-spin var(--etoile-spinner-duration) linear infinite;
323
+ flex-shrink: 0;
308
324
  }
309
325
 
310
326
  @keyframes etoile-spin {
@@ -314,33 +330,25 @@
314
330
  }
315
331
 
316
332
  /* ============================================
317
- Keyboard Shortcut Badge
333
+ Shared Error State
318
334
  ============================================ */
319
335
 
320
- .etoile-search .etoile-kbd {
321
- display: inline-flex;
322
- align-items: center;
323
- justify-content: center;
324
- height: var(--etoile-kbd-height);
325
- padding: 0 var(--etoile-kbd-padding-x);
326
- background: var(--etoile-bg-subtle);
327
- border: var(--etoile-input-border-width) solid var(--etoile-border);
328
- border-radius: var(--etoile-radius-kbd);
329
- color: var(--etoile-text-placeholder);
330
- font-size: var(--etoile-font-size-kbd);
331
- font-family: inherit;
332
- pointer-events: none;
336
+ .etoile-search [data-slot="searchbar-error"] {
337
+ padding: var(--etoile-empty-padding-y) var(--etoile-empty-padding-x);
338
+ text-align: center;
339
+ color: var(--etoile-text-muted);
333
340
  }
334
341
 
335
342
  /* ============================================
336
- Utility: Input with Icon
343
+ Inline Searchbar — Input Row
337
344
  ============================================ */
338
345
 
339
- .etoile-search .etoile-input-wrapper {
346
+ .etoile-search [data-slot="searchbar-input-row"] {
340
347
  position: relative;
341
348
  }
342
349
 
343
- .etoile-search .etoile-input-wrapper svg {
350
+ /* Icon absolutely positioned inside the row */
351
+ .etoile-search [data-slot="searchbar-input-row"] svg {
344
352
  position: absolute;
345
353
  top: 50%;
346
354
  left: var(--etoile-icon-offset);
@@ -352,26 +360,365 @@
352
360
  transition: color var(--etoile-transition);
353
361
  }
354
362
 
355
- .etoile-search .etoile-input-wrapper:focus-within svg {
363
+ .etoile-search [data-slot="searchbar-input-row"]:focus-within svg {
356
364
  color: var(--etoile-text);
357
365
  }
358
366
 
359
- .etoile-search .etoile-input-wrapper input[role="combobox"] {
367
+ /* Kbd absolutely positioned inside the row */
368
+ .etoile-search [data-slot="searchbar-input-row"] [data-slot="searchbar-kbd"] {
369
+ position: absolute;
370
+ top: 50%;
371
+ right: var(--etoile-kbd-offset);
372
+ transform: translateY(-50%);
373
+ }
374
+
375
+ /* Input — base styles */
376
+ .etoile-search [data-slot="searchbar-input-row"] input[role="combobox"] {
377
+ width: 100%;
378
+ height: var(--etoile-input-height);
379
+ padding: 0 var(--etoile-input-padding-x);
380
+ background: var(--etoile-bg);
381
+ border: var(--etoile-input-border-width) solid var(--etoile-border);
382
+ border-radius: var(--etoile-radius);
383
+ color: var(--etoile-text);
384
+ font-size: var(--etoile-font-size);
385
+ outline: none;
386
+ transition: all var(--etoile-transition);
387
+ }
388
+
389
+ .etoile-search
390
+ [data-slot="searchbar-input-row"]
391
+ input[role="combobox"]::placeholder {
392
+ color: var(--etoile-text-placeholder);
393
+ }
394
+
395
+ .etoile-search [data-slot="searchbar-input-row"] input[role="combobox"]:hover {
396
+ border-color: var(--etoile-border-hover);
397
+ }
398
+
399
+ .etoile-search [data-slot="searchbar-input-row"] input[role="combobox"]:focus {
400
+ border-color: var(--etoile-ring);
401
+ box-shadow: 0 0 0 var(--etoile-ring-width) var(--_etoile-focus-ring-light);
402
+ }
403
+
404
+ .dark
405
+ .etoile-search
406
+ [data-slot="searchbar-input-row"]
407
+ input[role="combobox"]:focus,
408
+ .etoile-search.dark
409
+ [data-slot="searchbar-input-row"]
410
+ input[role="combobox"]:focus {
411
+ box-shadow: 0 0 0 var(--etoile-ring-width) var(--_etoile-focus-ring-dark);
412
+ }
413
+
414
+ /* Increase left padding when icon is present */
415
+ .etoile-search
416
+ [data-slot="searchbar-input-row"]:has(svg)
417
+ input[role="combobox"] {
360
418
  padding-left: var(--etoile-input-padding-with-icon);
419
+ }
420
+
421
+ /* Increase right padding when kbd is present */
422
+ .etoile-search
423
+ [data-slot="searchbar-input-row"]:has([data-slot="searchbar-kbd"])
424
+ input[role="combobox"] {
361
425
  padding-right: var(--etoile-input-padding-with-kbd);
362
426
  }
363
427
 
364
- .etoile-search .etoile-input-wrapper .etoile-kbd {
428
+ /* ============================================
429
+ Inline Searchbar — Results Dropdown
430
+ ============================================ */
431
+
432
+ .etoile-search [role="listbox"] {
365
433
  position: absolute;
366
- top: 50%;
367
- right: var(--etoile-kbd-offset);
368
- transform: translateY(-50%);
434
+ display: flex;
435
+ flex-direction: column;
436
+ gap: var(--etoile-results-gap);
437
+ top: calc(100% + var(--etoile-results-offset));
438
+ scroll-padding-block-start: var(--etoile-scroll-padding);
439
+ scroll-padding-block-end: var(--etoile-scroll-padding);
440
+ left: 0;
441
+ right: 0;
442
+ z-index: var(--etoile-results-z-index);
443
+ max-height: var(--etoile-results-max-height);
444
+ overflow-y: auto;
445
+ padding: var(--etoile-results-padding);
446
+ background: var(--etoile-bg);
447
+ border: var(--etoile-input-border-width) solid var(--etoile-border);
448
+ border-radius: var(--etoile-radius);
449
+ box-shadow: var(--etoile-shadow-lg);
450
+ animation: etoile-list-in var(--etoile-animation-duration)
451
+ cubic-bezier(0.16, 1, 0.3, 1);
452
+ scrollbar-width: none;
453
+ -ms-overflow-style: none;
454
+ }
455
+
456
+ .etoile-search [role="listbox"]::-webkit-scrollbar {
457
+ display: none;
458
+ }
459
+
460
+ @keyframes etoile-list-in {
461
+ from {
462
+ opacity: 0;
463
+ transform: translateY(-6px) scale(0.98);
464
+ }
465
+ to {
466
+ opacity: 1;
467
+ transform: translateY(0) scale(1);
468
+ }
369
469
  }
370
470
 
371
471
  /* ============================================
372
- Utility: Input without Icon (just kbd)
472
+ Modal Overlay + Content panel
373
473
  ============================================ */
374
474
 
375
- .etoile-search .etoile-input-wrapper:not(:has(svg)) input[role="combobox"] {
376
- padding-left: var(--etoile-input-padding-x);
475
+ .etoile-search[data-slot="searchbar-overlay"] {
476
+ position: fixed;
477
+ inset: 0;
478
+ background: var(--etoile-overlay-bg);
479
+ z-index: calc(var(--etoile-content-z-index) - 1);
480
+ }
481
+
482
+ .etoile-search[data-slot="searchbar-overlay"][data-state="open"] {
483
+ animation: etoile-overlay-in var(--etoile-overlay-duration)
484
+ cubic-bezier(0.16, 1, 0.3, 1);
485
+ }
486
+
487
+ .etoile-search[data-slot="searchbar-overlay"][data-state="closed"] {
488
+ animation: etoile-overlay-out var(--etoile-overlay-duration)
489
+ cubic-bezier(0.16, 1, 0.3, 1) forwards;
490
+ pointer-events: none;
491
+ }
492
+
493
+ .dark .etoile-search[data-slot="searchbar-overlay"][data-state="open"],
494
+ .etoile-search.dark[data-slot="searchbar-overlay"][data-state="open"] {
495
+ background: var(--etoile-overlay-bg-dark);
496
+ }
497
+
498
+ .etoile-search[data-slot="searchbar-content"] {
499
+ position: fixed;
500
+ top: var(--etoile-content-top);
501
+ left: 50%;
502
+ transform: translateX(-50%);
503
+ width: var(--etoile-content-width);
504
+ max-height: var(--etoile-content-max-height);
505
+ padding: var(--etoile-content-padding);
506
+ background: var(--etoile-bg);
507
+ border: 1px solid var(--etoile-content-border-color);
508
+ border-radius: var(--etoile-radius);
509
+ box-shadow: var(--etoile-shadow-lg);
510
+ z-index: var(--etoile-content-z-index);
511
+ overflow: hidden;
512
+ /* Height is set by JS (ResizeObserver) and animated via transition */
513
+ transition: height var(--etoile-content-height-duration)
514
+ cubic-bezier(0.16, 1, 0.3, 1);
515
+ }
516
+
517
+ /* Inner wrapper carries the flex layout so ResizeObserver can measure natural content height */
518
+ .etoile-search[data-slot="searchbar-content"]
519
+ [data-slot="searchbar-content-inner"] {
520
+ display: flex;
521
+ flex-direction: column;
522
+ gap: var(--etoile-content-padding);
523
+ }
524
+
525
+ .etoile-search[data-slot="searchbar-content"][data-state="open"] {
526
+ animation: etoile-content-in var(--etoile-content-duration)
527
+ cubic-bezier(0.16, 1, 0.3, 1);
528
+ }
529
+
530
+ .etoile-search[data-slot="searchbar-content"][data-state="closed"] {
531
+ animation: etoile-content-out var(--etoile-content-duration)
532
+ cubic-bezier(0.16, 1, 0.3, 1) forwards;
533
+ pointer-events: none;
534
+ }
535
+
536
+ .dark .etoile-search[data-slot="searchbar-content"],
537
+ .etoile-search.dark[data-slot="searchbar-content"] {
538
+ background: var(--etoile-content-bg-dark);
539
+ border-color: var(--etoile-content-border-color-dark);
540
+ }
541
+
542
+ /* Listbox becomes a static child inside the modal panel.
543
+ max-height caps the list so content above it remains visible. */
544
+ .etoile-search[data-slot="searchbar-content"][data-state="open"]
545
+ [role="listbox"] {
546
+ position: static;
547
+ top: auto;
548
+ left: auto;
549
+ right: auto;
550
+ max-height: var(--etoile-content-list-max-height);
551
+ min-height: 0;
552
+ overflow-y: auto;
553
+ border: 0;
554
+ border-top: 1px solid var(--etoile-content-list-border-color);
555
+ border-radius: 0;
556
+ box-shadow: none;
557
+ margin-top: calc(
558
+ -1 * var(--etoile-content-padding) + var(--etoile-content-list-offset)
559
+ );
560
+ margin-left: calc(-1 * var(--etoile-content-padding));
561
+ margin-right: calc(-1 * var(--etoile-content-padding));
562
+ margin-bottom: calc(-1 * var(--etoile-content-padding));
563
+ padding: var(--etoile-content-padding);
564
+ gap: var(--etoile-results-gap);
565
+ scrollbar-width: none;
566
+ -ms-overflow-style: none;
567
+ scroll-padding-block-start: var(--etoile-scroll-padding);
568
+ scroll-padding-block-end: var(--etoile-scroll-padding);
569
+ animation: etoile-list-in var(--etoile-animation-duration)
570
+ cubic-bezier(0.16, 1, 0.3, 1);
571
+ }
572
+
573
+ .dark
574
+ .etoile-search[data-slot="searchbar-content"][data-state="open"]
575
+ [role="listbox"],
576
+ .etoile-search.dark[data-slot="searchbar-content"][data-state="open"]
577
+ [role="listbox"] {
578
+ border-top-color: var(--etoile-content-list-border-color-dark);
579
+ }
580
+
581
+ /* Extra padding for empty/loading states inside the modal */
582
+ .etoile-search[data-slot="searchbar-content"] [data-slot="searchbar-empty"],
583
+ .etoile-search[data-slot="searchbar-content"] [data-slot="searchbar-loading"],
584
+ .etoile-search[data-slot="searchbar-content"] [data-state="empty"],
585
+ .etoile-search[data-slot="searchbar-content"] [data-state="loading"] {
586
+ padding: var(--etoile-content-padding);
587
+ }
588
+
589
+ /* ============================================
590
+ Modal — Input Row (Searchbar.ModalInput)
591
+ ============================================ */
592
+
593
+ .etoile-search[data-slot="searchbar-content"]
594
+ [data-slot="searchbar-modal-input"] {
595
+ display: flex;
596
+ align-items: center;
597
+ gap: var(--etoile-content-padding);
598
+ min-height: var(--etoile-input-height);
599
+ padding-inline: var(--etoile-modal-input-padding-x);
600
+ padding-block: var(
601
+ --etoile-modal-input-padding-y,
602
+ var(--etoile-modal-input-padding-x)
603
+ );
604
+ }
605
+
606
+ .etoile-search[data-slot="searchbar-content"]
607
+ [data-slot="searchbar-modal-input"]
608
+ svg {
609
+ flex-shrink: 0;
610
+ color: var(--etoile-text-muted);
611
+ transition: color var(--etoile-transition);
612
+ }
613
+
614
+ .etoile-search[data-slot="searchbar-content"]
615
+ [data-slot="searchbar-modal-input"]:focus-within
616
+ svg {
617
+ color: var(--etoile-text);
618
+ }
619
+
620
+ .etoile-search[data-slot="searchbar-content"]
621
+ [data-slot="searchbar-modal-input"]
622
+ [data-slot="searchbar-kbd"] {
623
+ flex-shrink: 0;
624
+ }
625
+
626
+ /* Modal input: borderless, transparent, fills available width */
627
+ .etoile-search[data-slot="searchbar-content"]
628
+ [data-slot="searchbar-modal-input"]
629
+ input[role="combobox"] {
630
+ flex: 1;
631
+ min-width: 0;
632
+ height: auto;
633
+ padding: 0;
634
+ font-size: var(--etoile-modal-input-font-size);
635
+ font-weight: var(--etoile-modal-input-font-weight);
636
+ background: transparent;
637
+ border: none;
638
+ box-shadow: none;
639
+ outline: none;
640
+ color: inherit;
641
+ }
642
+
643
+ .etoile-search[data-slot="searchbar-content"]
644
+ [data-slot="searchbar-modal-input"]
645
+ input[role="combobox"]::placeholder {
646
+ color: var(--etoile-text-placeholder);
647
+ }
648
+
649
+ .etoile-search[data-slot="searchbar-content"]
650
+ [data-slot="searchbar-modal-input"]
651
+ input[role="combobox"]:hover,
652
+ .etoile-search[data-slot="searchbar-content"]
653
+ [data-slot="searchbar-modal-input"]
654
+ input[role="combobox"]:focus {
655
+ border: none;
656
+ box-shadow: none;
657
+ outline: none;
658
+ }
659
+
660
+ /* ============================================
661
+ Animations
662
+ ============================================ */
663
+
664
+ @keyframes etoile-overlay-in {
665
+ from {
666
+ opacity: 0;
667
+ }
668
+ to {
669
+ opacity: 1;
670
+ }
671
+ }
672
+
673
+ @keyframes etoile-overlay-out {
674
+ from {
675
+ opacity: 1;
676
+ }
677
+ to {
678
+ opacity: 0;
679
+ }
680
+ }
681
+
682
+ @keyframes etoile-content-in {
683
+ from {
684
+ opacity: 0;
685
+ transform: translateX(-50%) scale(var(--etoile-content-enter-scale));
686
+ }
687
+ to {
688
+ opacity: 1;
689
+ transform: translateX(-50%) scale(1);
690
+ }
691
+ }
692
+
693
+ @keyframes etoile-content-out {
694
+ from {
695
+ opacity: 1;
696
+ transform: translateX(-50%) scale(1);
697
+ }
698
+ to {
699
+ opacity: 0;
700
+ transform: translateX(-50%) scale(var(--etoile-content-enter-scale));
701
+ }
702
+ }
703
+
704
+ @media (max-width: 640px) {
705
+ /* Mobile command palette: comfortable top offset + 24px side gutters */
706
+ .etoile-search {
707
+ --etoile-content-top: 24px;
708
+ --etoile-content-width: calc(100vw - 48px);
709
+ }
710
+ }
711
+
712
+ @media (prefers-reduced-motion: reduce) {
713
+ .etoile-search [role="listbox"],
714
+ .etoile-search[data-slot="searchbar-overlay"][data-state="open"],
715
+ .etoile-search[data-slot="searchbar-overlay"][data-state="closed"],
716
+ .etoile-search[data-slot="searchbar-content"],
717
+ .etoile-search[data-slot="searchbar-content"][data-state="open"],
718
+ .etoile-search[data-slot="searchbar-content"][data-state="closed"],
719
+ .etoile-search[data-slot="searchbar-content"][data-state="open"]
720
+ [role="listbox"] {
721
+ animation: none !important;
722
+ transition: none !important;
723
+ }
377
724
  }