Skip to content

Commit 9eec1b4

Browse files
sarahdayanclaude
andcommitted
feat(autocomplete): add configurable headers for recent searches and suggestions
Add section header support to the autocomplete widget with granular theming controls. Recent searches and query suggestions now have dedicated config fields with header templates. The theme system gets new header variables for color, font, spacing, text-transform, and letter-spacing, replacing the old hardcoded panel-gap-based margins and removing dead CSS for non-existent DOM elements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d13ffcb commit 9eec1b4

File tree

12 files changed

+596
-83
lines changed

12 files changed

+596
-83
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './carousel-item';
22
export * from './list-item';
3+
export * from './section-header';
34
export * from './utils';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { TemplateParams } from 'instantsearch.js/es/types';
2+
3+
export function renderSectionHeader(label: string) {
4+
return function header(
5+
_data: { items: unknown[] },
6+
{ html }: TemplateParams
7+
) {
8+
return html`<span>${label}</span>`;
9+
};
10+
}

packages/runtime/src/experiences/widget.tsx

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ import hierarchicalMenu from 'instantsearch.js/es/widgets/hierarchical-menu/hier
2828
import numericMenu from 'instantsearch.js/es/widgets/numeric-menu/numeric-menu';
2929

3030
import { renderTool } from './renderer';
31-
import { renderCarouselItem, renderListItem, SKELETON_CSS } from './templates';
31+
import {
32+
renderCarouselItem,
33+
renderListItem,
34+
renderSectionHeader,
35+
SKELETON_CSS,
36+
} from './templates';
3237
import type { ExperienceWidget } from './types';
3338

3439
import type { ChatTransport } from 'instantsearch.js/es/connectors/chat/connectChat';
@@ -131,20 +136,45 @@ export default (function experience(
131136
'ais.autocomplete': {
132137
widget: EXPERIMENTAL_autocomplete,
133138
async transformParams(params) {
134-
const { showSuggestions, ...rest } = params as typeof params & {
135-
showSuggestions?: {
136-
searchPageUrl?: string;
137-
queryParam?: string;
138-
indexName?: string;
139+
const { showRecent, showSuggestions, ...rest } =
140+
params as typeof params & {
141+
showRecent?: boolean | { templates?: { header?: string } };
142+
showSuggestions?: {
143+
searchPageUrl?: string;
144+
queryParam?: string;
145+
// oxlint-disable-next-line id-length -- backward compat for old configs
146+
q?: string;
147+
indexName?: string;
148+
templates?: { header?: string };
149+
};
139150
};
140-
};
151+
152+
const showRecentTransformed = showRecent
153+
? typeof showRecent === 'object' && showRecent.templates?.header
154+
? {
155+
templates: {
156+
header: renderSectionHeader(showRecent.templates.header),
157+
},
158+
}
159+
: {}
160+
: undefined;
141161

142162
return Promise.resolve({
143163
...rest,
164+
...(showRecent ? { showRecent: showRecentTransformed } : {}),
144165
...(showSuggestions
145166
? {
146167
showSuggestions: {
147168
indexName: showSuggestions.indexName,
169+
...(showSuggestions.templates?.header
170+
? {
171+
templates: {
172+
header: renderSectionHeader(
173+
showSuggestions.templates.header
174+
),
175+
},
176+
}
177+
: {}),
148178
...(showSuggestions.searchPageUrl
149179
? {
150180
getURL: (item: { query: string }) => {
@@ -153,7 +183,9 @@ export default (function experience(
153183
window.location.origin
154184
);
155185
url.searchParams.set(
156-
showSuggestions.queryParam ?? 'q',
186+
showSuggestions.queryParam ??
187+
showSuggestions.q ??
188+
'q',
157189
item.query
158190
);
159191
return url.toString();

packages/theme/src/widgets/autocomplete/autocomplete.css

Lines changed: 10 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -343,50 +343,23 @@
343343

344344
/* --- Source header --- */
345345
.ais-AutocompleteIndexHeader {
346-
margin: var(--ais-autocomplete-panel-gap) 0.5em
347-
var(--ais-autocomplete-panel-gap) 0;
348-
padding: 0;
349-
position: relative;
350-
}
351-
352-
.ais-AutocompleteIndexHeader:empty {
353-
display: none;
354-
}
355-
356-
.ais-AutocompleteIndexHeaderTitle {
357-
background: rgba(
358-
var(--ais-autocomplete-background-color),
359-
var(--ais-autocomplete-background-color-alpha)
360-
);
361346
color: rgba(
362347
var(--ais-autocomplete-header-color),
363-
var(--ais-autocomplete-primary-color-alpha)
348+
var(--ais-autocomplete-header-color-alpha)
364349
);
365-
display: inline-block;
366350
font-size: var(--ais-autocomplete-header-font-size);
367351
font-weight: var(--ais-autocomplete-header-font-weight);
368-
margin: 0;
369-
padding: 0 var(--ais-autocomplete-panel-gap) 0 0;
370-
position: relative;
371-
z-index: 2;
352+
letter-spacing: var(--ais-autocomplete-header-letter-spacing);
353+
margin: var(--ais-autocomplete-header-margin-top)
354+
var(--ais-autocomplete-header-margin-right)
355+
var(--ais-autocomplete-header-margin-bottom)
356+
var(--ais-autocomplete-header-margin-left);
357+
padding: 0;
358+
text-transform: var(--ais-autocomplete-header-text-transform);
372359
}
373360

374-
.ais-AutocompleteIndexHeaderLine {
375-
border-bottom: solid var(--ais-autocomplete-border-width)
376-
rgba(
377-
var(--ais-autocomplete-header-color),
378-
var(--ais-autocomplete-primary-color-alpha)
379-
);
380-
display: block;
381-
height: calc(var(--ais-autocomplete-border-width) * 2);
382-
left: 0;
383-
margin: 0;
384-
opacity: var(--ais-autocomplete-header-line-opacity);
385-
padding: 0;
386-
position: absolute;
387-
right: 0;
388-
top: var(--ais-autocomplete-panel-gap);
389-
z-index: 1;
361+
.ais-AutocompleteIndexHeader:empty {
362+
display: none;
390363
}
391364

392365
/* --- Items --- */

packages/theme/src/widgets/autocomplete/presets.ts

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
5252
'autocomplete-item-selected-opacity': 0.06,
5353
'autocomplete-item-padding': 4,
5454
'autocomplete-item-gap': 6,
55-
'autocomplete-header-line-opacity': 0.15,
55+
'autocomplete-header-margin-top': 4,
56+
'autocomplete-header-margin-bottom': 2,
57+
'autocomplete-header-text-transform': 'none',
58+
'autocomplete-header-letter-spacing': 0,
5659
'autocomplete-detached-modal-border-radius': 12,
5760
'autocomplete-scrollbar-width': 3,
5861
'autocomplete-scrollbar-color-mix': 15,
@@ -89,7 +92,10 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
8992
'autocomplete-item-selected-opacity': 0.1,
9093
'autocomplete-item-padding': 4,
9194
'autocomplete-item-gap': 6,
92-
'autocomplete-header-line-opacity': 0.2,
95+
'autocomplete-header-margin-top': 4,
96+
'autocomplete-header-margin-bottom': 2,
97+
'autocomplete-header-text-transform': 'none',
98+
'autocomplete-header-letter-spacing': 0,
9399
'autocomplete-detached-modal-border-radius': 12,
94100
'autocomplete-scrollbar-width': 3,
95101
'autocomplete-scrollbar-color-mix': 15,
@@ -130,7 +136,9 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
130136
'autocomplete-item-border-radius': 10,
131137
'autocomplete-item-selected-opacity': 0.06,
132138
'autocomplete-item-padding': 6,
133-
'autocomplete-header-line-opacity': 0.15,
139+
'autocomplete-header-margin-top': 10,
140+
'autocomplete-header-margin-bottom': 4,
141+
'autocomplete-header-margin-left': 6,
134142
'autocomplete-header-font-size': 0.72,
135143
'autocomplete-detached-modal-border-radius': 20,
136144
'autocomplete-transition-duration': 0.25,
@@ -166,7 +174,9 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
166174
'autocomplete-item-border-radius': 10,
167175
'autocomplete-item-selected-opacity': 0.1,
168176
'autocomplete-item-padding': 6,
169-
'autocomplete-header-line-opacity': 0.15,
177+
'autocomplete-header-margin-top': 10,
178+
'autocomplete-header-margin-bottom': 4,
179+
'autocomplete-header-margin-left': 6,
170180
'autocomplete-header-font-size': 0.72,
171181
'autocomplete-detached-modal-border-radius': 20,
172182
'autocomplete-transition-duration': 0.25,
@@ -195,6 +205,7 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
195205
'autocomplete-highlight-weight': 900,
196206
'autocomplete-header-font-weight': 700,
197207
'autocomplete-header-font-size': 0.72,
208+
'autocomplete-header-letter-spacing': 0.1,
198209
'autocomplete-form-border-radius': 0,
199210
'autocomplete-panel-border-radius': 0,
200211
'autocomplete-item-border-radius': 0,
@@ -206,7 +217,8 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
206217
'autocomplete-form-padding-left': 20,
207218
'autocomplete-panel-shadow': [shadow(4, 4, 0, 0, '10, 10, 10', 0.15)],
208219
'autocomplete-item-selected-opacity': 0.08,
209-
'autocomplete-header-line-opacity': 0.4,
220+
'autocomplete-header-margin-top': 10,
221+
'autocomplete-header-margin-bottom': 4,
210222
'autocomplete-transition-duration': 0.12,
211223
'autocomplete-scrollbar-color-mix': 40,
212224
},
@@ -225,6 +237,7 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
225237
'autocomplete-highlight-weight': 900,
226238
'autocomplete-header-font-weight': 700,
227239
'autocomplete-header-font-size': 0.72,
240+
'autocomplete-header-letter-spacing': 0.1,
228241
'autocomplete-form-border-radius': 0,
229242
'autocomplete-panel-border-radius': 0,
230243
'autocomplete-item-border-radius': 0,
@@ -238,7 +251,8 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
238251
shadow(4, 4, 0, 0, '240, 240, 240', 0.15),
239252
],
240253
'autocomplete-item-selected-opacity': 0.12,
241-
'autocomplete-header-line-opacity': 0.4,
254+
'autocomplete-header-margin-top': 10,
255+
'autocomplete-header-margin-bottom': 4,
242256
'autocomplete-transition-duration': 0.12,
243257
'autocomplete-scrollbar-color-mix': 40,
244258
},
@@ -280,7 +294,11 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
280294
shadow(0, 8, 24, -4, '100, 116, 139', 0.12),
281295
],
282296
'autocomplete-highlight-weight': 600,
283-
'autocomplete-header-line-opacity': 0.2,
297+
'autocomplete-header-margin-top': 12,
298+
'autocomplete-header-margin-bottom': 6,
299+
'autocomplete-header-margin-left': 8,
300+
'autocomplete-header-text-transform': 'none',
301+
'autocomplete-header-letter-spacing': 0,
284302
'autocomplete-transition-duration': 0.4,
285303
'autocomplete-transition-timing-function':
286304
'cubic-bezier(0.22, 1, 0.36, 1)',
@@ -318,7 +336,11 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
318336
shadow(0, 8, 24, -4, '0, 0, 0', 0.3),
319337
],
320338
'autocomplete-highlight-weight': 600,
321-
'autocomplete-header-line-opacity': 0.2,
339+
'autocomplete-header-margin-top': 12,
340+
'autocomplete-header-margin-bottom': 6,
341+
'autocomplete-header-margin-left': 8,
342+
'autocomplete-header-text-transform': 'none',
343+
'autocomplete-header-letter-spacing': 0,
322344
'autocomplete-transition-duration': 0.4,
323345
'autocomplete-transition-timing-function':
324346
'cubic-bezier(0.22, 1, 0.36, 1)',
@@ -362,8 +384,10 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
362384
shadow(0, 4, 12, 0, '58, 50, 43', 0.04),
363385
],
364386
'autocomplete-item-selected-opacity': 0.06,
365-
'autocomplete-header-line-opacity': 0.15,
387+
'autocomplete-header-margin-top': 6,
388+
'autocomplete-header-margin-bottom': 2,
366389
'autocomplete-header-font-size': 0.7,
390+
'autocomplete-header-letter-spacing': 0.08,
367391
'autocomplete-scrollbar-width': 3,
368392
'autocomplete-scrollbar-color-mix': 15,
369393
},
@@ -397,8 +421,10 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
397421
shadow(0, 4, 12, 0, '0, 0, 0', 0.1),
398422
],
399423
'autocomplete-item-selected-opacity': 0.1,
400-
'autocomplete-header-line-opacity': 0.2,
424+
'autocomplete-header-margin-top': 6,
425+
'autocomplete-header-margin-bottom': 2,
401426
'autocomplete-header-font-size': 0.7,
427+
'autocomplete-header-letter-spacing': 0.08,
402428
'autocomplete-scrollbar-width': 3,
403429
'autocomplete-scrollbar-color-mix': 15,
404430
},
@@ -434,7 +460,6 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
434460
shadow(0, 6, 20, -4, '20, 60, 50', 0.1),
435461
],
436462
'autocomplete-item-selected-opacity': 0.07,
437-
'autocomplete-header-line-opacity': 0.2,
438463
'autocomplete-scrollbar-color-mix': 20,
439464
},
440465
dark: {
@@ -462,7 +487,6 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
462487
shadow(0, 6, 20, -4, '0, 0, 0', 0.3),
463488
],
464489
'autocomplete-item-selected-opacity': 0.12,
465-
'autocomplete-header-line-opacity': 0.25,
466490
'autocomplete-scrollbar-color-mix': 20,
467491
},
468492
},
@@ -507,7 +531,10 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
507531
'autocomplete-highlight-weight': 700,
508532
'autocomplete-header-font-weight': 700,
509533
'autocomplete-header-font-size': 0.75,
510-
'autocomplete-header-line-opacity': 0.2,
534+
'autocomplete-header-margin-top': 10,
535+
'autocomplete-header-margin-bottom': 4,
536+
'autocomplete-header-margin-left': 6,
537+
'autocomplete-header-letter-spacing': 0.08,
511538
'autocomplete-transition-duration': 0.2,
512539
'autocomplete-scrollbar-width': 4,
513540
'autocomplete-scrollbar-color-mix': 25,
@@ -548,7 +575,10 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
548575
'autocomplete-highlight-weight': 700,
549576
'autocomplete-header-font-weight': 700,
550577
'autocomplete-header-font-size': 0.75,
551-
'autocomplete-header-line-opacity': 0.2,
578+
'autocomplete-header-margin-top': 10,
579+
'autocomplete-header-margin-bottom': 4,
580+
'autocomplete-header-margin-left': 6,
581+
'autocomplete-header-letter-spacing': 0.08,
552582
'autocomplete-transition-duration': 0.2,
553583
'autocomplete-scrollbar-width': 4,
554584
'autocomplete-scrollbar-color-mix': 25,
@@ -575,6 +605,7 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
575605
'autocomplete-highlight-weight': 900,
576606
'autocomplete-header-font-weight': 800,
577607
'autocomplete-header-font-size': 0.72,
608+
'autocomplete-header-letter-spacing': 0.12,
578609
'autocomplete-form-border-radius': 0,
579610
'autocomplete-panel-border-radius': 0,
580611
'autocomplete-item-border-radius': 0,
@@ -586,7 +617,8 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
586617
'autocomplete-form-padding-left': 20,
587618
'autocomplete-panel-shadow': [shadow(5, 5, 0, 0, '10, 10, 10', 1)],
588619
'autocomplete-item-selected-opacity': 0.12,
589-
'autocomplete-header-line-opacity': 0.5,
620+
'autocomplete-header-margin-top': 10,
621+
'autocomplete-header-margin-bottom': 4,
590622
'autocomplete-transition-duration': 0.08,
591623
'autocomplete-scrollbar-width': 8,
592624
'autocomplete-scrollbar-color-mix': 50,
@@ -607,6 +639,7 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
607639
'autocomplete-highlight-weight': 900,
608640
'autocomplete-header-font-weight': 800,
609641
'autocomplete-header-font-size': 0.72,
642+
'autocomplete-header-letter-spacing': 0.12,
610643
'autocomplete-form-border-radius': 0,
611644
'autocomplete-panel-border-radius': 0,
612645
'autocomplete-item-border-radius': 0,
@@ -618,7 +651,8 @@ export const AUTOCOMPLETE_PRESETS: ThemePreset[] = [
618651
'autocomplete-form-padding-left': 20,
619652
'autocomplete-panel-shadow': [shadow(5, 5, 0, 0, '245, 245, 245', 1)],
620653
'autocomplete-item-selected-opacity': 0.15,
621-
'autocomplete-header-line-opacity': 0.5,
654+
'autocomplete-header-margin-top': 10,
655+
'autocomplete-header-margin-bottom': 4,
622656
'autocomplete-transition-duration': 0.08,
623657
'autocomplete-scrollbar-width': 8,
624658
'autocomplete-scrollbar-color-mix': 50,

0 commit comments

Comments
 (0)