@chassis-ui/docs 0.3.4 → 0.3.6

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Shared Astro layouts, components, and utilities for Chassis documentation sites.
4
4
 
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Version: 0.3.4](https://img.shields.io/badge/Version-0.3.4-blue.svg)](https://www.npmjs.com/package/@chassis-ui/docs) [![npm](https://img.shields.io/npm/v/@chassis-ui/docs.svg)](https://www.npmjs.com/package/@chassis-ui/docs)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Version: 0.3.6](https://img.shields.io/badge/Version-0.3.6-blue.svg)](https://www.npmjs.com/package/@chassis-ui/docs) [![npm](https://img.shields.io/npm/v/@chassis-ui/docs.svg)](https://www.npmjs.com/package/@chassis-ui/docs)
6
6
 
7
7
  ## Overview
8
8
 
@@ -26,11 +26,19 @@ npm install @chassis-ui/docs
26
26
 
27
27
  ```json
28
28
  {
29
- "astro": "^5.0.0"
29
+ "@astrojs/markdown-remark": "^7.0.0",
30
+ "@chassis-ui/css": ">=0.3.2",
31
+ "@pagefind/component-ui": "^1.0.0",
32
+ "@shikijs/transformers": "^4.3.1",
33
+ "astro": "^7.0.0",
34
+ "htmlparser2": "^12.0.0",
35
+ "rehype-autolink-headings": "^7.0.0",
36
+ "sass": "^1.101.0",
37
+ "shiki": "^4.3.1"
30
38
  }
31
39
  ```
32
40
 
33
- In practice, sites also install the rest of the Chassis stack — `@chassis-ui/css`, `@chassis-ui/tokens`, `@chassis-ui/icons` — to render the layouts and styles correctly.
41
+ In practice, sites also install the rest of the Chassis stack — `@chassis-ui/tokens`, `@chassis-ui/icons` — to render the layouts and styles correctly.
34
42
 
35
43
  ## Usage
36
44
 
@@ -55,7 +63,7 @@ import { generateTOC, processImage } from '@chassis-ui/docs'
55
63
 
56
64
  | Subpath | Contents |
57
65
  | ------------------------------- | --------------------------------------------------------------------------------------------- |
58
- | `@chassis-ui/docs` | Library functions: `chassis`, `image`, `layout`, `rehype`, `toc`, `utils` |
66
+ | `@chassis-ui/docs` | Library functions: `highlight`, `image`, `layout`, `markdown`, `placeholder`, `rehype`, `shortcodes`, `site`, `toc`, `utils` |
59
67
  | `@chassis-ui/docs/layouts/*` | `BaseLayout`, `DocsLayout`, `RedirectLayout`, `SingleLayout` |
60
68
  | `@chassis-ui/docs/components/*` | `DocsSidebar`, `FeatureCard`, `NavLink`, `ResponsiveImage`, `TableOfContents`, `ThemeToggler` |
61
69
  | `@chassis-ui/docs/shortcodes/*` | MDX shortcode components |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@chassis-ui/docs",
3
3
  "description": "Shared Astro components and layouts for Chassis documentation sites.",
4
- "version": "0.3.4",
4
+ "version": "0.3.6",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -54,11 +54,6 @@
54
54
  "sass": "^1.101.0",
55
55
  "shiki": "^4.3.1"
56
56
  },
57
- "peerDependenciesMeta": {
58
- "@pagefind/component-ui": {
59
- "optional": true
60
- }
61
- },
62
57
  "dependencies": {
63
58
  "github-slugger": "^2.0.0",
64
59
  "image-size": "^2.0.2",
@@ -71,7 +66,6 @@
71
66
  "devDependencies": {
72
67
  "@types/hast": "^3.0.4",
73
68
  "astro": "^7.0.6",
74
- "unified": "^11.0.5",
75
- "vite": "^8.1.3"
69
+ "unified": "^11.0.5"
76
70
  }
77
71
  }
package/src/js/search.js CHANGED
@@ -22,7 +22,163 @@ const LOADING_SKELETON_DELAY_MS = 200
22
22
  const RECENT_STORAGE_KEY = 'cxd:search:recent'
23
23
  const RECENT_LIMIT = 5
24
24
 
25
- const instance = getInstanceManager().getInstance('default')
25
+ // ─── Multi-site search ────────────────────────────────────────────────────────
26
+ // Every Chassis project deploys its own Pagefind index under its own path
27
+ // prefix (e.g. `/tokens/pagefind/`), matching the prefix chassis-ui.com's
28
+ // Vercel rewrites proxy that project under (see `ref/VERCEL_CONFIG.md`).
29
+ // This lets one shared instance merge every project's index into a single,
30
+ // site-filterable search — no cross-origin requests required, since
31
+ // `chassis-ui.com/<slug>/...` and `<slug>.vercel.app/<slug>/...` both resolve
32
+ // the same root-relative bundle path.
33
+
34
+ /** All Chassis projects with their own Pagefind index, in nav order. `website` is the hub (root-level index, no path prefix). */
35
+ const CHASSIS_SITES = [
36
+ { slug: 'website', label: 'Website' },
37
+ { slug: 'tokens', label: 'Tokens' },
38
+ { slug: 'css', label: 'CSS' },
39
+ { slug: 'assets', label: 'Assets' },
40
+ { slug: 'icons', label: 'Icons' },
41
+ { slug: 'figma', label: 'Figma' }
42
+ ]
43
+
44
+ // Hosts where every project's index is *expected* to be reachable at its
45
+ // root-relative bundle path via Vercel's proxy rewrites. Everywhere else
46
+ // (local dev, direct `*.vercel.app` preview deployments) only this site's own
47
+ // pages exist, so merging would always 404 — not attempted there at all.
48
+ const CANONICAL_HOSTS = new Set(['chassis-ui.com', 'staging.chassis-ui.com'])
49
+
50
+ const getBundlePath = (slug) => (slug === 'website' ? '/pagefind/' : `/${slug}/pagefind/`)
51
+
52
+ const getCurrentSite = () => {
53
+ const [, firstSegment] = typeof location === 'undefined' ? [] : location.pathname.split('/')
54
+ return CHASSIS_SITES.find((site) => site.slug === firstSegment) ?? CHASSIS_SITES[0]
55
+ }
56
+
57
+ const isCanonicalHost = () =>
58
+ typeof location !== 'undefined' && CANONICAL_HOSTS.has(location.hostname)
59
+
60
+ const currentSite = getCurrentSite()
61
+
62
+ // Pagefind derives `baseUrl` from `bundlePath` by stripping the trailing
63
+ // `pagefind` segment (e.g. `/tokens/pagefind/` -> `/tokens/`), then prepends
64
+ // that to every result's raw URL. Those raw URLs are already site-root-relative
65
+ // (e.g. `tokens/docs/...`, because that's where the page actually lives inside
66
+ // `_site`), so the auto-derived baseUrl would double up the prefix into
67
+ // `/tokens/tokens/docs/...`. Force it back to `/` for every index.
68
+ const BASE_URL = '/'
69
+
70
+ // NOTE: we deliberately do NOT pass `mergeIndex` here. `@pagefind/component-ui`
71
+ // awaits every entry in that array back-to-back with no try/catch — if a
72
+ // single sibling site's index 404s (e.g. mid-rollout, one repo deployed
73
+ // before another), the whole `__doLoad__` throws and search breaks entirely,
74
+ // including this site's own results. We instead merge additional sites
75
+ // ourselves afterwards, in `mergeAdditionalSites()` below, so a single
76
+ // unreachable sibling is skipped rather than taking everything down.
77
+ const instance = getInstanceManager().getInstance('default', {
78
+ bundlePath: getBundlePath(currentSite.slug),
79
+ baseUrl: BASE_URL,
80
+ mergeFilter: { site: currentSite.slug }
81
+ })
82
+
83
+ // Default filter scope: the website (hub) defaults to searching everywhere;
84
+ // every project site defaults to its own results, but can still be widened
85
+ // or switched via the `cxd-search-filter` control.
86
+ if (currentSite.slug !== 'website') {
87
+ instance.searchFilters = { site: currentSite.slug }
88
+ }
89
+
90
+ let mergeAttempted = false
91
+
92
+ // How long to wait for a sibling's Pagefind entry file before assuming it's
93
+ // unreachable (bad deploy, rollout in progress, host down, etc.).
94
+ const PROBE_TIMEOUT_MS = 4000
95
+
96
+ // Mirrors Pagefind's own language selection in `PagefindInstance.findIndex`:
97
+ // exact `<html lang>` match, then its base subtag, then the language with the
98
+ // most pages. Needed so the probe below checks the same chunk `init()` will
99
+ // actually request.
100
+ const getPreferredLanguageHash = (entry) => {
101
+ const languages = entry?.languages
102
+ if (!languages) return null
103
+ const htmlLang = document.documentElement.getAttribute('lang')?.toLowerCase()
104
+ if (htmlLang && languages[htmlLang]) return languages[htmlLang].hash
105
+ const base = htmlLang?.split('-')[0]
106
+ if (base && languages[base]) return languages[base].hash
107
+ const [topLanguage] = Object.values(languages).sort((a, b) => b.page_count - a.page_count)
108
+ return topLanguage?.hash ?? null
109
+ }
110
+
111
+ /**
112
+ * Checks whether a sibling site's Pagefind bundle actually exists, without
113
+ * touching the shared `pf` instance. This matters because a *failed*
114
+ * `pf.mergeIndex()` call corrupts that instance beyond recovery — there is no
115
+ * public API to detach a bad merge, and every future search (including this
116
+ * site's own) throws the same error afterwards.
117
+ *
118
+ * `init()` performs two fetches, not one: `pagefind-entry.json`, then the
119
+ * language-specific `pagefind.<hash>.pf_meta` chunk it points to. Checking
120
+ * only the entry file misses exactly the failure this is meant to guard
121
+ * against — a deploy mid-rollout where the entry file has already updated to
122
+ * reference a hash whose chunk hasn't finished uploading (or was purged) yet.
123
+ * So we probe both before ever calling `mergeIndex`.
124
+ */
125
+ const isBundleReachable = async (bundlePath) => {
126
+ const controller = new AbortController()
127
+ const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS)
128
+ try {
129
+ const entryResponse = await fetch(`${bundlePath}pagefind-entry.json`, {
130
+ signal: controller.signal
131
+ })
132
+ if (!entryResponse.ok) return false
133
+
134
+ const hash = getPreferredLanguageHash(await entryResponse.json())
135
+ if (!hash) return false
136
+
137
+ const metaResponse = await fetch(`${bundlePath}pagefind.${hash}.pf_meta`, {
138
+ signal: controller.signal
139
+ })
140
+ return metaResponse.ok
141
+ } catch {
142
+ return false
143
+ } finally {
144
+ clearTimeout(timer)
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Loads this site's own Pagefind bundle, then merges every other Chassis
150
+ * site's index in, one at a time. Each sibling is probed first (see
151
+ * `isBundleReachable`) so an unreachable one — a bad deploy, a rollout still
152
+ * in progress across the (separate) Chassis repos, etc. — is silently skipped
153
+ * instead of breaking search for everyone. Safe to call multiple times; only
154
+ * does real work once.
155
+ */
156
+ const mergeAdditionalSites = async () => {
157
+ if (mergeAttempted) return
158
+ mergeAttempted = true
159
+
160
+ await instance.triggerLoad()
161
+ const pf = instance.__pagefind__
162
+ if (!pf || !isCanonicalHost()) return
163
+
164
+ const siblings = CHASSIS_SITES.filter((site) => site.slug !== currentSite.slug)
165
+ await Promise.allSettled(
166
+ siblings.map(async (site) => {
167
+ const bundlePath = getBundlePath(site.slug)
168
+ if (!(await isBundleReachable(bundlePath))) {
169
+ console.warn(
170
+ `Chassis search: "${site.slug}" index not reachable at ${bundlePath}, skipping`
171
+ )
172
+ return
173
+ }
174
+ try {
175
+ await pf.mergeIndex(bundlePath, { baseUrl: BASE_URL, mergeFilter: { site: site.slug } })
176
+ } catch (error) {
177
+ console.warn(`Chassis search: failed to merge the "${site.slug}" index`, error)
178
+ }
179
+ })
180
+ )
181
+ }
26
182
 
27
183
  // ─── Recent visits ────────────────────────────────────────────────────────────
28
184
  // Opt-out via Do Not Track; silently no-op when storage is unavailable.
@@ -70,20 +226,40 @@ const HTML_ESCAPES = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'
70
226
  const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => HTML_ESCAPES[char])
71
227
 
72
228
  // Single template for every clickable row: top-level results, sub-results, and
73
- // recently visited items. `title` and `excerpt` are inlined as-is so callers
74
- // can either escape them (recents, plain text) or pass Pagefind's pre-marked HTML.
75
- const renderItem = ({ url, title, excerpt = '', icon, sub = false }) => `
229
+ // recently visited items. `titleHtml`/`excerptHtml` name the contract, not just
230
+ // describe it: callers must pass already-safe HTML, either by running plain
231
+ // text through `escapeHtml` (recents) or by using Pagefind's own pre-marked
232
+ // excerpt (search results, which embed `<mark>` highlights).
233
+ const renderItem = ({ url, titleHtml, excerptHtml = '', icon, sub = false }) => `
76
234
  <a class="cxd-search-item${sub ? ' cxd-search-subitem' : ''}" href="${escapeHtml(url)}">
77
235
  <svg class="icon cxd-search-item-icon" width="16" height="16" aria-hidden="true">
78
- <use href="#${icon}"></use>
236
+ <use href="/static/icons/chassis-icons.svg#${icon}"></use>
79
237
  </svg>
80
238
  <div class="cxd-search-item-body">
81
- <div class="cxd-search-item-title">${title}</div>
82
- ${excerpt ? `<div class="cxd-search-item-excerpt">${excerpt}</div>` : ''}
239
+ <div class="cxd-search-item-title">${titleHtml}</div>
240
+ ${excerptHtml ? `<div class="cxd-search-item-excerpt">${excerptHtml}</div>` : ''}
83
241
  </div>
84
242
  </a>
85
243
  `
86
244
 
245
+ // Shared wrapper for every rendered list of rows (recents, top-level results).
246
+ const renderResultsList = (itemsHtml) =>
247
+ `<ol class="list flush cxd-search-results-list">${itemsHtml}</ol>`
248
+
249
+ // Wires `handlers` (eventType -> bound listener) onto `target` and returns a
250
+ // cleanup function that removes them all — the bind/track/remove dance every
251
+ // element below otherwise repeats by hand in connectedCallback/disconnectedCallback.
252
+ const addListeners = (target, handlers) => {
253
+ for (const [type, handler] of Object.entries(handlers)) {
254
+ target.addEventListener(type, handler)
255
+ }
256
+ return () => {
257
+ for (const [type, handler] of Object.entries(handlers)) {
258
+ target.removeEventListener(type, handler)
259
+ }
260
+ }
261
+ }
262
+
87
263
  // ─── Custom elements ──────────────────────────────────────────────────────────
88
264
 
89
265
  class CxdSearchInput extends HTMLElement {
@@ -94,23 +270,16 @@ class CxdSearchInput extends HTMLElement {
94
270
  this.inputEl = this.input
95
271
  instance.registerInput(this, { keyboardNavigation: true })
96
272
 
97
- this._onInput = this._onInput.bind(this)
98
- this._onKeydown = this._onKeydown.bind(this)
99
- this.input.addEventListener('input', this._onInput)
100
- this.input.addEventListener('keydown', this._onKeydown)
101
-
102
- this.input.addEventListener(
103
- 'focus',
104
- () => {
105
- instance.triggerLoad()
106
- },
107
- { once: true }
108
- )
273
+ this._removeListeners = addListeners(this.input, {
274
+ input: this._onInput.bind(this),
275
+ keydown: this._onKeydown.bind(this)
276
+ })
277
+
278
+ this.input.addEventListener('focus', () => mergeAdditionalSites(), { once: true })
109
279
  }
110
280
 
111
281
  disconnectedCallback() {
112
- this.input?.removeEventListener('input', this._onInput)
113
- this.input?.removeEventListener('keydown', this._onKeydown)
282
+ this._removeListeners?.()
114
283
  }
115
284
 
116
285
  _onInput(event) {
@@ -146,17 +315,16 @@ class CxdSearchResults extends HTMLElement {
146
315
  instance.on('results', (result) => this._renderResults(result), this)
147
316
  instance.on('error', (error) => this._renderError(error), this)
148
317
 
149
- this._onKeydown = this._onKeydown.bind(this)
150
- this._onClick = this._onClick.bind(this)
151
- this.addEventListener('keydown', this._onKeydown)
152
- this.addEventListener('click', this._onClick)
318
+ this._removeListeners = addListeners(this, {
319
+ keydown: this._onKeydown.bind(this),
320
+ click: this._onClick.bind(this)
321
+ })
153
322
 
154
323
  this._renderEmpty()
155
324
  }
156
325
 
157
326
  disconnectedCallback() {
158
- this.removeEventListener('keydown', this._onKeydown)
159
- this.removeEventListener('click', this._onClick)
327
+ this._removeListeners?.()
160
328
  this._clearLoadingTimer()
161
329
  }
162
330
 
@@ -250,24 +418,24 @@ class CxdSearchResults extends HTMLElement {
250
418
  <span class="cxd-search-recent-label">Recently visited</span>
251
419
  <button type="button" class="cxd-search-recent-clear" data-cxd-search-clear-recent>Clear</button>
252
420
  </div>
253
- <ol class="list flush cxd-search-results-list">
254
- ${recents
421
+ ${renderResultsList(
422
+ recents
255
423
  .map((visit) => {
256
424
  const isSubLink = visit.url.includes('#')
257
425
  return `
258
426
  <li class="cxd-search-result">
259
427
  ${renderItem({
260
428
  url: visit.url,
261
- title: escapeHtml(visit.title),
262
- excerpt: escapeHtml(visit.excerpt),
429
+ titleHtml: escapeHtml(visit.title),
430
+ excerptHtml: escapeHtml(visit.excerpt),
263
431
  icon: isSubLink ? 'hashtag-outline' : 'file-outline',
264
432
  sub: isSubLink
265
433
  })}
266
434
  </li>
267
435
  `
268
436
  })
269
- .join('')}
270
- </ol>
437
+ .join('')
438
+ )}
271
439
  </div>
272
440
  `
273
441
  }
@@ -331,11 +499,7 @@ class CxdSearchResults extends HTMLElement {
331
499
 
332
500
  if (instance.searchTerm !== term) return // newer search started
333
501
 
334
- this.innerHTML = `
335
- <ol class="list flush cxd-search-results-list">
336
- ${data.map((result) => this._renderResult(result)).join('')}
337
- </ol>
338
- `
502
+ this.innerHTML = renderResultsList(data.map((result) => this._renderResult(result)).join(''))
339
503
  }
340
504
 
341
505
  _renderResult(result) {
@@ -351,7 +515,7 @@ class CxdSearchResults extends HTMLElement {
351
515
  .map(
352
516
  (sub) => `
353
517
  <li class="list-item">
354
- ${renderItem({ url: sub.url, title: escapeHtml(sub.title), excerpt: sub.excerpt, icon: 'hashtag-outline', sub: true })}
518
+ ${renderItem({ url: sub.url, titleHtml: escapeHtml(sub.title), excerptHtml: sub.excerpt, icon: 'hashtag-outline', sub: true })}
355
519
  </li>
356
520
  `
357
521
  )
@@ -361,13 +525,51 @@ class CxdSearchResults extends HTMLElement {
361
525
 
362
526
  return `
363
527
  <li class="list-item cxd-search-result">
364
- ${renderItem({ url: result.url, title: escapeHtml(title), excerpt: result.excerpt, icon: 'file-outline' })}
528
+ ${renderItem({ url: result.url, titleHtml: escapeHtml(title), excerptHtml: result.excerpt, icon: 'file-outline' })}
365
529
  ${subResultsHtml}
366
530
  </li>
367
531
  `
368
532
  }
369
533
  }
370
534
 
535
+ // ─── Site filter ──────────────────────────────────────────────────────────────
536
+ // Dropdown letting the visitor scope results to a single project, or back out
537
+ // to "Everywhere". Always rendered (see `CxdSearchFilter` below) — filtering to
538
+ // a site that hasn't actually been merged in just yields no results.
539
+
540
+ const FILTER_OPTIONS = [{ slug: '', label: 'Everywhere' }, ...CHASSIS_SITES]
541
+
542
+ class CxdSearchFilter extends HTMLElement {
543
+ connectedCallback() {
544
+ this.select = this.querySelector('select')
545
+ if (!this.select) return
546
+
547
+ // Always rendered — even outside merging (local dev, direct preview
548
+ // deployments) the current site's own results are tagged with `site`, so
549
+ // "Everywhere" and the current site both work; switching to a *different*
550
+ // site just yields no results until it's actually merged in.
551
+ this.hidden = false
552
+
553
+ this.select.innerHTML = FILTER_OPTIONS.map(
554
+ (option) => `<option value="${option.slug}">${escapeHtml(option.label)}</option>`
555
+ ).join('')
556
+ this.select.value = currentSite.slug === 'website' ? '' : currentSite.slug
557
+
558
+ this._removeListeners = addListeners(this.select, {
559
+ change: this._onChange.bind(this)
560
+ })
561
+ }
562
+
563
+ disconnectedCallback() {
564
+ this._removeListeners?.()
565
+ }
566
+
567
+ _onChange() {
568
+ const value = this.select.value
569
+ instance.triggerSearchWithFilters(instance.searchTerm, value ? { site: value } : {})
570
+ }
571
+ }
572
+
371
573
  // ─── Registration ─────────────────────────────────────────────────────────────
372
574
 
373
575
  const defineSearchCustomElements = () => {
@@ -377,6 +579,9 @@ const defineSearchCustomElements = () => {
377
579
  if (!customElements.get('cxd-search-results')) {
378
580
  customElements.define('cxd-search-results', CxdSearchResults)
379
581
  }
582
+ if (!customElements.get('cxd-search-filter')) {
583
+ customElements.define('cxd-search-filter', CxdSearchFilter)
584
+ }
380
585
  }
381
586
 
382
587
  // ─── Trigger shortcut hints ───────────────────────────────────────────────────
@@ -44,6 +44,10 @@ const { id = 'cxdSearchDialog', placeholder = 'Search docs…' } = Astro.props
44
44
  />
45
45
  </div>
46
46
  </cxd-search-input>
47
+ <cxd-search-filter class="cxd-search-filter" hidden>
48
+ <label class="visually-hidden" for={`${id}-filter`}>Search in</label>
49
+ <select id={`${id}-filter`} class="form-input cxd-search-filter-select"></select>
50
+ </cxd-search-filter>
47
51
  <button
48
52
  type="button"
49
53
  class="close-button"
@@ -1,418 +1,434 @@
1
1
  @use "@chassis-ui/css/scss/mixins" as *;
2
2
  @use "@chassis-ui/css/scss/placeholders" as *;
3
3
 
4
- //
5
- // Pagefind search UI
6
- //
7
-
8
- .cxd-search {
9
- position: relative;
10
-
11
- @include media-breakpoint-up(large) {
12
- position: absolute;
13
- top: 20%;
14
- right: 13rem;
15
- width: 200px;
16
-
17
- [dir="rtl"] & {
18
- right: auto;
19
- left: 13rem;
4
+ @layer custom {
5
+ //
6
+ // Pagefind search UI
7
+ //
8
+
9
+ .cxd-search {
10
+ position: relative;
11
+
12
+ @include media-breakpoint-up(large) {
13
+ position: absolute;
14
+ top: 20%;
15
+ right: 13rem;
16
+ width: 200px;
17
+
18
+ [dir="rtl"] & {
19
+ right: auto;
20
+ left: 13rem;
21
+ }
20
22
  }
23
+
21
24
  }
22
25
 
23
- }
24
26
 
27
+ // Trigger button
28
+ //
29
+ // Mobile (< md): icon-only, no chrome.
30
+ // md+: expands to a faux input with label and ⌘K shortcut hint.
25
31
 
26
- // Trigger button
27
- //
28
- // Mobile (< md): icon-only, no chrome.
29
- // md+: expands to a faux input with label and ⌘K shortcut hint.
30
-
31
- .cxd-search-trigger {
32
- // @extend %form-input;
33
- --cx-padding-x: var(--cx-space-xsmall);
34
- --cx-padding-y: .375rem;
35
- --cx-icon-color: var(--cx-fg-color);
36
- display: inline-flex;
37
- gap: .5rem;
38
- align-items: center;
39
- margin-block: .125rem;
40
- // padding-inline-end: .5rem;
41
- // padding: .25rem .5rem;
42
- @include exclude-strokes(form-input);
43
- @include font-size(var(--cx-font-size-small));
44
- color: var(--cx-fg-subtle);
45
- text-align: start;
46
- cursor: pointer;
47
- background-color: transparent;
48
- border: 0;
49
- @include border-radius(var(--cx-border-radius-medium));
50
- @include transition(color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out);
51
-
52
- &:focus-visible {
53
- @include focus-ring(true);
54
- }
32
+ .cxd-search-trigger {
33
+ // @extend %form-input;
34
+ --cx-padding-x: var(--cx-space-xsmall);
35
+ --cx-padding-y: .375rem;
36
+ --cx-icon-color: var(--cx-fg-color);
37
+ display: inline-flex;
38
+ gap: .5rem;
39
+ align-items: center;
40
+ margin-block: .125rem;
41
+ // padding-inline-end: .5rem;
42
+ // padding: .25rem .5rem;
43
+ @include exclude-strokes(form-input);
44
+ @include font-size(var(--cx-font-size-small));
45
+ color: var(--cx-fg-subtle);
46
+ text-align: start;
47
+ cursor: pointer;
48
+ background-color: transparent;
49
+ border: 0;
50
+ @include border-radius(var(--cx-border-radius-medium));
51
+ @include transition(color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out);
55
52
 
56
- @include media-breakpoint-up(large) {
57
- --cx-border-color: var(--cx-border-subtle);
58
- --cx-icon-size: var(--cx-icon-small);
59
- --cx-icon-color: var(--cx-fg-subtle);
60
- width: 100%;
61
- background-color: var(--cx-bg-main);
62
- @include border();
53
+ &:focus-visible {
54
+ @include focus-ring(true);
55
+ }
63
56
 
64
- &:hover {
65
- // --fg-color: var(--cx-fg-main);
66
- --cx-icon-color: var(--cx-fg-color);
67
- --cx-border-color: var(--cx-border-main);
57
+ @include media-breakpoint-up(large) {
58
+ --cx-border-color: var(--cx-border-subtle);
59
+ --cx-icon-size: var(--cx-icon-small);
60
+ --cx-icon-color: var(--cx-fg-subtle);
61
+ width: 100%;
62
+ background-color: var(--cx-bg-main);
63
+ @include border();
64
+
65
+ &:hover {
66
+ // --fg-color: var(--cx-fg-main);
67
+ --cx-icon-color: var(--cx-fg-color);
68
+ --cx-border-color: var(--cx-border-main);
69
+ }
68
70
  }
69
71
  }
70
- }
71
72
 
72
- .cxd-search-trigger-icon {
73
- flex-shrink: 0;
74
- color: var(--cx-fg-subtle);
75
- }
73
+ .cxd-search-trigger-icon {
74
+ flex-shrink: 0;
75
+ color: var(--cx-fg-subtle);
76
+ }
76
77
 
77
- .cxd-search-trigger-label,
78
- .cxd-search-trigger-shortcut {
79
- display: none;
78
+ .cxd-search-trigger-label,
79
+ .cxd-search-trigger-shortcut {
80
+ display: none;
80
81
 
81
- @include media-breakpoint-up(large) {
82
- display: inline-flex;
82
+ @include media-breakpoint-up(large) {
83
+ display: inline-flex;
84
+ }
83
85
  }
84
- }
85
86
 
86
- .cxd-search-trigger-label {
87
- flex: 1;
88
- overflow: hidden;
89
- text-overflow: ellipsis;
90
- white-space: nowrap;
91
- }
87
+ .cxd-search-trigger-label {
88
+ flex: 1;
89
+ overflow: hidden;
90
+ text-overflow: ellipsis;
91
+ white-space: nowrap;
92
+ }
92
93
 
93
- .cxd-search-trigger-shortcut {
94
- flex-shrink: 0;
95
- gap: .125rem;
96
- }
94
+ .cxd-search-trigger-shortcut {
95
+ flex-shrink: 0;
96
+ gap: .125rem;
97
+ }
97
98
 
98
- .cxd-search-trigger-key {
99
- display: inline-flex;
100
- align-items: center;
101
- justify-content: center;
102
- min-width: 1.25rem;
103
- padding: .125rem .375rem;
104
- font-family: var(--cx-font-family-code);
105
- @include font-size(var(--cx-font-size-small));
106
- @include line-height(var(--cx-line-height-small));
107
- color: var(--cx-fg-subtle);
108
- background-color: var(--cx-bg-evident);
109
- // border: var(--cx-border-width) solid var(--cx-border-color);
110
- @include border-radius(var(--cx-border-radius-small));
111
- }
99
+ .cxd-search-trigger-key {
100
+ display: inline-flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ min-width: 1.25rem;
104
+ padding: .125rem .375rem;
105
+ font-family: var(--cx-font-family-code);
106
+ @include font-size(var(--cx-font-size-small));
107
+ @include line-height(var(--cx-line-height-small));
108
+ color: var(--cx-fg-subtle);
109
+ background-color: var(--cx-bg-evident);
110
+ // border: var(--cx-border-width) solid var(--cx-border-color);
111
+ @include border-radius(var(--cx-border-radius-small));
112
+ }
112
113
 
113
- // Search dialog shell
114
- //
115
- // Top-aligned overlay using the existing `.dialog` surface.
116
- // Sizing and position make it feel like a command palette.
114
+ // Search dialog shell
115
+ //
116
+ // Top-aligned overlay using the existing `.dialog` surface.
117
+ // Sizing and position make it feel like a command palette.
118
+
119
+ .cxd-search-dialog {
120
+ --cx-dialog-margin: 1.5rem;
121
+ --cx-dialog-padding: .75rem;
122
+ --cx-dialog-header-padding: .75rem;
123
+ --cx-dialog-footer-padding: .5rem .75rem;
124
+ --cx-dialog-border-radius: var(--cx-border-radius-large);
125
+
126
+ align-self: flex-start;
127
+ width: 640px;
128
+ max-height: min(80dvh, 720px);
129
+ margin-block-start: 3rem;
130
+ overflow: hidden;
131
+ background-color: var(--cx-bg-even);
132
+
133
+ @include media-breakpoint-down(small) {
134
+ max-height: calc(100dvh - 4rem);
135
+ }
136
+ }
117
137
 
118
- .cxd-search-dialog {
119
- --cx-dialog-margin: 1.5rem;
120
- --cx-dialog-padding: .75rem;
121
- --cx-dialog-header-padding: .75rem;
122
- --cx-dialog-footer-padding: .5rem .75rem;
123
- --cx-dialog-border-radius: var(--cx-border-radius-large);
138
+ // Header / search form
124
139
 
125
- align-self: flex-start;
126
- width: 640px;
127
- max-height: min(80dvh, 720px);
128
- margin-block-start: 3rem;
129
- overflow: hidden;
130
- background-color: var(--cx-bg-even);
140
+ .cxd-search-form {
141
+ display: flex;
142
+ flex-wrap: nowrap;
143
+ gap: .75rem;
144
+ align-items: center;
145
+ padding: var(--cx-space-medium);
146
+ border-bottom: 0;
147
+ }
131
148
 
132
- @include media-breakpoint-down(small) {
133
- max-height: calc(100dvh - 4rem);
149
+ cxd-search-input {
150
+ flex-grow: 1;
134
151
  }
135
- }
136
152
 
137
- // Header / search form
153
+ // Site filter dropdown — only shown when other project indexes were merged
154
+ // in (see `search.js`), letting the visitor scope results to one project or
155
+ // back out to "Everywhere".
156
+ .cxd-search-filter {
157
+ flex-shrink: 0;
158
+ }
138
159
 
139
- .cxd-search-form {
140
- display: flex;
141
- flex-wrap: nowrap;
142
- gap: .75rem;
143
- align-items: center;
144
- padding: var(--cx-space-medium);
145
- border-bottom: 0;
146
- }
160
+ // .cxd-search-filter-select {
161
+ // --cx-padding-y: .375rem;
162
+ // --cx-padding-x: .5rem;
163
+ // width: auto;
164
+ // @include font-size(var(--cx-font-size-small));
165
+ // }
166
+ // .cxd-search-input {
167
+ // flex: 1;
168
+ // width: 100%;
169
+ // min-width: 0;
170
+ // background-color: transparent;
171
+ // border-color: transparent;
172
+ // box-shadow: none;
173
+
174
+ // &:focus {
175
+ // background-color: transparent;
176
+ // border-color: transparent;
177
+ // box-shadow: none;
178
+ // }
179
+ // }
147
180
 
148
- cxd-search-input {
149
- width: 100%;
150
- }
151
- // .cxd-search-input {
152
- // flex: 1;
153
- // width: 100%;
154
- // min-width: 0;
155
- // background-color: transparent;
156
- // border-color: transparent;
157
- // box-shadow: none;
158
-
159
- // &:focus {
160
- // background-color: transparent;
161
- // border-color: transparent;
162
- // box-shadow: none;
163
- // }
164
- // }
165
-
166
- // .cxd-search-close {
167
- // flex-shrink: 0;
168
- // margin-inline-start: 0;
169
- // }
170
-
171
- // Body / results
172
-
173
- .cxd-search-body {
174
- padding: 0 var(--cx-space-medium) var(--cx-space-medium);
175
- }
181
+ // .cxd-search-close {
182
+ // flex-shrink: 0;
183
+ // margin-inline-start: 0;
184
+ // }
176
185
 
177
- .cxd-search-results {
178
- display: block;
179
- min-height: 6rem;
180
- border: 0;
181
- }
186
+ // Body / results
182
187
 
183
- .cxd-search-results-list {
184
- // --cx-list-border-width: 0;
185
- // --cx-list-border-radius: 0;
188
+ .cxd-search-body {
189
+ padding: 0 var(--cx-space-medium) var(--cx-space-medium);
190
+ }
186
191
 
187
- gap: var(--cx-space-small);
188
- padding-block-start: var(--cx-space-xsmall);
189
- list-style: none;
190
- .list-item {
191
- padding: 0;
192
+ .cxd-search-results {
193
+ display: block;
194
+ min-height: 6rem;
192
195
  border: 0;
193
196
  }
194
- }
195
197
 
196
- .cxd-search-result {
197
- display: flex;
198
- flex-direction: column;
199
- // gap: var(--cx-space-xsmall);
200
- gap: 0;
201
- padding: 0;
202
- }
198
+ .cxd-search-results-list {
199
+ // --cx-list-border-width: 0;
200
+ // --cx-list-border-radius: 0;
203
201
 
204
- // Default-active state: while focus is in the search input, the first result
205
- // is highlighted as the Enter target. Once the user arrows into the list,
206
- // :focus-within takes over.
207
- .cxd-search-results:not(:focus-within) .cxd-search-result:first-child > .cxd-search-item {
208
- @include focus-ring(true);
209
- --cx-focus-ring-offset: -2px;
210
- background-color: var(--cx-bg-main);
211
- }
212
-
213
- // Single row template: top-level results, sub-results, and recent visits.
214
- .cxd-search-item {
215
- --cx-icon-size: var(--cx-icon-medium);
216
- --cx-icon-color: var(--cx-fg-subtle);
217
- position: relative;
218
- display: grid;
219
- grid-template-columns: auto 1fr;
220
- column-gap: .5rem;
221
- align-items: center;
222
- padding: var(--cx-space-xsmall);
223
- text-decoration: none;
224
- background-color: var(--cx-bg-main);
225
- @include border();
226
- @include border-radius(var(--cx-border-radius-medium));
227
-
228
- mark {
229
- background: none;
202
+ gap: var(--cx-space-small);
203
+ padding-block-start: var(--cx-space-xsmall);
204
+ list-style: none;
205
+ .list-item {
206
+ padding: 0;
207
+ border: 0;
208
+ }
230
209
  }
231
210
 
232
- &:has(+ .cxd-search-subresults) {
233
- border-block-end-width: 0;
234
- @include border-bottom-radius(0);
211
+ .cxd-search-result {
212
+ display: flex;
213
+ flex-direction: column;
214
+ // gap: var(--cx-space-xsmall);
215
+ gap: 0;
216
+ padding: 0;
235
217
  }
236
218
 
237
- &:hover {
219
+ // Default-active state: while focus is in the search input, the first result
220
+ // is highlighted as the Enter target. Once the user arrows into the list,
221
+ // :focus-within takes over.
222
+ .cxd-search-results:not(:focus-within) .cxd-search-result:first-child > .cxd-search-item {
223
+ @include focus-ring(true);
224
+ --cx-focus-ring-offset: -2px;
238
225
  background-color: var(--cx-bg-main);
239
226
  }
240
227
 
241
- &:focus-visible {
242
- --cx-focus-ring-offset: -2px;
243
- z-index: 1;
244
- @include focus-ring(true);
245
- }
228
+ // Single row template: top-level results, sub-results, and recent visits.
229
+ .cxd-search-item {
230
+ --cx-icon-size: var(--cx-icon-medium);
231
+ --cx-icon-color: var(--cx-fg-subtle);
232
+ position: relative;
233
+ display: grid;
234
+ grid-template-columns: auto 1fr;
235
+ column-gap: .5rem;
236
+ align-items: center;
237
+ padding: var(--cx-space-xsmall);
238
+ text-decoration: none;
239
+ background-color: var(--cx-bg-main);
240
+ @include border();
241
+ @include border-radius(var(--cx-border-radius-medium));
246
242
 
247
- // &:not(.cxd-search-item-sub) {
248
- // border: var(--cx-border-width) solid var(--cx-border-color);
249
- // }
250
- }
243
+ mark {
244
+ background: none;
245
+ }
251
246
 
252
- .cxd-search-item-icon {
253
- // margin-block-start: .15rem;
254
- color: var(--cx-fg-subtle);
255
- }
247
+ &:has(+ .cxd-search-subresults) {
248
+ border-block-end-width: 0;
249
+ @include border-bottom-radius(0);
250
+ }
256
251
 
257
- .cxd-search-item-body {
258
- grid-column: 2;
259
- min-width: 0;
260
- }
252
+ &:hover {
253
+ background-color: var(--cx-bg-main);
254
+ }
261
255
 
262
- .cxd-search-item-title {
263
- font-weight: var(--cx-font-weight-strong);
264
- color: var(--cx-fg-main);
265
- }
256
+ &:focus-visible {
257
+ --cx-focus-ring-offset: -2px;
258
+ z-index: 1;
259
+ @include focus-ring(true);
260
+ }
266
261
 
267
- .cxd-search-item-excerpt {
268
- overflow: hidden;
269
- color: var(--cx-fg-subtle);
270
- text-overflow: ellipsis;
271
- white-space: nowrap;
272
- @include font-size(var(--cx-font-size-small));
273
- @include line-height(var(--cx-line-height-small));
274
- }
262
+ // &:not(.cxd-search-item-sub) {
263
+ // border: var(--cx-border-width) solid var(--cx-border-color);
264
+ // }
265
+ }
266
+
267
+ .cxd-search-item-icon {
268
+ // margin-block-start: .15rem;
269
+ color: var(--cx-fg-subtle);
270
+ }
275
271
 
276
- .cxd-search-subitem {
277
- --cx-icon-size: var(--cx-icon-small);
278
- --cx-icon-color: var(--cx-fg-subtle);
279
- align-items: center;
280
- padding-inline-start: var(--cx-space-4xlarge);
281
- // padding-inline-start: var(--cx-space-medium);
282
-
283
- &::before {
284
- position: absolute;
285
- inset-block: 15%;
286
- inset-inline: 0;
287
- width: var(--cx-border-width);
288
- height: 70%;
289
- margin-inline-start: 1.25rem;
290
- content: " ";
291
- background-color: var(--cx-fg-subtle);
292
- }
293
- &::after {
294
- position: absolute;
295
- inset: 0;
296
- width: .75rem;
297
- height: var(--cx-border-width);
298
- margin-block: auto;
299
- margin-inline-start: 1.25rem;
300
- content: " ";
301
- background-color: var(--cx-fg-subtle);
272
+ .cxd-search-item-body {
273
+ grid-column: 2;
274
+ min-width: 0;
302
275
  }
276
+
303
277
  .cxd-search-item-title {
278
+ font-weight: var(--cx-font-weight-strong);
279
+ color: var(--cx-fg-main);
280
+ }
281
+
282
+ .cxd-search-item-excerpt {
283
+ overflow: hidden;
284
+ color: var(--cx-fg-subtle);
285
+ text-overflow: ellipsis;
286
+ white-space: nowrap;
304
287
  @include font-size(var(--cx-font-size-small));
305
288
  @include line-height(var(--cx-line-height-small));
306
289
  }
307
290
 
308
- }
309
-
310
- .cxd-search-subresults {
311
- display: flex;
312
- flex-direction: column;
313
- // gap: var(--cx-space-xsmall);
314
- gap: 0;
315
- // padding-inline-start: var(--cx-space-large);
316
291
  .cxd-search-subitem {
317
- border-block-end-width: 0;
318
- @include border-radius(0);
319
- &:is(:last-child > .cxd-search-subitem) {
320
- border-block-end-width: var(--cx-border-width);
321
- @include border-bottom-radius(var(--cx-border-radius));
322
- &::before {
323
- height: 35%;
324
- }
292
+ --cx-icon-size: var(--cx-icon-small);
293
+ --cx-icon-color: var(--cx-fg-subtle);
294
+ align-items: center;
295
+ padding-inline-start: var(--cx-space-4xlarge);
296
+ // padding-inline-start: var(--cx-space-medium);
297
+
298
+ &::before {
299
+ position: absolute;
300
+ inset-block: 15%;
301
+ inset-inline: 0;
302
+ width: var(--cx-border-width);
303
+ height: 70%;
304
+ margin-inline-start: 1.25rem;
305
+ content: " ";
306
+ background-color: var(--cx-fg-subtle);
307
+ }
308
+ &::after {
309
+ position: absolute;
310
+ inset: 0;
311
+ width: .75rem;
312
+ height: var(--cx-border-width);
313
+ margin-block: auto;
314
+ margin-inline-start: 1.25rem;
315
+ content: " ";
316
+ background-color: var(--cx-fg-subtle);
317
+ }
318
+ .cxd-search-item-title {
319
+ @include font-size(var(--cx-font-size-small));
320
+ @include line-height(var(--cx-line-height-small));
325
321
  }
326
322
 
327
323
  }
328
- }
329
324
 
330
- // Recently visited
325
+ .cxd-search-subresults {
326
+ display: flex;
327
+ flex-direction: column;
328
+ // gap: var(--cx-space-xsmall);
329
+ gap: 0;
330
+ // padding-inline-start: var(--cx-space-large);
331
+ .cxd-search-subitem {
332
+ border-block-end-width: 0;
333
+ @include border-radius(0);
334
+ &:is(:last-child > .cxd-search-subitem) {
335
+ border-block-end-width: var(--cx-border-width);
336
+ @include border-bottom-radius(var(--cx-border-radius));
337
+ &::before {
338
+ height: 35%;
339
+ }
340
+ }
331
341
 
332
- .cxd-search-recent-header {
333
- display: flex;
334
- gap: .5rem;
335
- align-items: center;
336
- justify-content: space-between;
337
- padding-block: .5rem;
338
- }
342
+ }
343
+ }
339
344
 
340
- .cxd-search-recent-label {
341
- @include font-size(var(--cx-font-size-xsmall));
342
- font-weight: 600;
343
- color: var(--cx-fg-subtle);
344
- text-transform: uppercase;
345
- letter-spacing: .04em;
346
- }
345
+ // Recently visited
347
346
 
348
- .cxd-search-recent-clear {
349
- padding: .125rem .375rem;
350
- @include font-size(var(--cx-font-size-xsmall));
351
- color: var(--cx-fg-subtle);
352
- cursor: pointer;
353
- background-color: transparent;
354
- border: 0;
355
- @include border-radius(var(--cx-border-radius-medium));
356
- @include transition(color .15s ease-in-out, background-color .15s ease-in-out);
357
-
358
- &:hover {
359
- color: var(--cx-fg-main);
360
- background-color: var(--cx-bg-main);
347
+ .cxd-search-recent-header {
348
+ display: flex;
349
+ gap: .5rem;
350
+ align-items: center;
351
+ justify-content: space-between;
352
+ padding-block: .5rem;
361
353
  }
362
354
 
363
- &:focus-visible {
364
- @include focus-ring(true);
355
+ .cxd-search-recent-label {
356
+ @include font-size(var(--cx-font-size-xsmall));
357
+ font-weight: 600;
358
+ color: var(--cx-fg-subtle);
359
+ text-transform: uppercase;
360
+ letter-spacing: .04em;
365
361
  }
366
- }
367
362
 
368
- // Empty / loading / error states
363
+ .cxd-search-recent-clear {
364
+ padding: .125rem .375rem;
365
+ @include font-size(var(--cx-font-size-xsmall));
366
+ color: var(--cx-fg-subtle);
367
+ cursor: pointer;
368
+ background-color: transparent;
369
+ border: 0;
370
+ @include border-radius(var(--cx-border-radius-medium));
371
+ @include transition(color .15s ease-in-out, background-color .15s ease-in-out);
369
372
 
370
- .cxd-search-empty,
371
- .cxd-search-error {
372
- padding: 2rem 1rem;
373
- @include font-size(var(--cx-font-size-small));
374
- color: var(--cx-fg-subtle);
375
- text-align: center;
376
- }
373
+ &:hover {
374
+ color: var(--cx-fg-main);
375
+ background-color: var(--cx-bg-main);
376
+ }
377
377
 
378
- .cxd-search-error {
379
- color: var(--cx-danger-fg-main);
380
- }
378
+ &:focus-visible {
379
+ @include focus-ring(true);
380
+ }
381
+ }
381
382
 
382
- .cxd-search-loading {
383
- display: flex;
384
- flex-direction: column;
385
- gap: var(--cx-space-small);
386
- }
383
+ // Empty / loading / error states
387
384
 
385
+ .cxd-search-empty,
386
+ .cxd-search-error {
387
+ padding: 2rem 1rem;
388
+ @include font-size(var(--cx-font-size-small));
389
+ color: var(--cx-fg-subtle);
390
+ text-align: center;
391
+ }
388
392
 
389
- .cxd-search-footer {
390
- padding-block: var(--cx-space-small);
391
- background-color: var(--cx-bg-evident);
392
- }
393
+ .cxd-search-error {
394
+ color: var(--cx-danger-fg-main);
395
+ }
393
396
 
394
- .cxd-search-shortcuts-list > .list-item {
395
- display: inline-flex;
396
- gap: .25rem;
397
- align-items: center;
398
- padding: 0 .5rem;
399
- color: var(--cx-fg-subtle);
400
- background: none;
401
- border: 0;
402
- }
397
+ .cxd-search-loading {
398
+ display: flex;
399
+ flex-direction: column;
400
+ gap: var(--cx-space-small);
401
+ }
403
402
 
404
- .cxd-search-shortcut-key {
405
- display: inline-flex;
406
- align-items: center;
407
- justify-content: center;
408
- min-width: 1.25rem;
409
- min-height: 1.25rem;
410
- padding: 0 .25rem;
411
- background-color: var(--cx-bg-main);
412
- }
413
403
 
414
- .cxd-search-shortcut-label {
415
- padding-inline-start: .25rem;
416
- @include font-size(var(--cx-font-size-small));
417
- @include line-height(var(--cx-line-height-small));
404
+ .cxd-search-footer {
405
+ padding-block: var(--cx-space-small);
406
+ background-color: var(--cx-bg-evident);
407
+ }
408
+
409
+ .cxd-search-shortcuts-list > .list-item {
410
+ display: inline-flex;
411
+ gap: .25rem;
412
+ align-items: center;
413
+ padding: 0 .5rem;
414
+ color: var(--cx-fg-subtle);
415
+ background: none;
416
+ border: 0;
417
+ }
418
+
419
+ .cxd-search-shortcut-key {
420
+ display: inline-flex;
421
+ align-items: center;
422
+ justify-content: center;
423
+ min-width: 1.25rem;
424
+ min-height: 1.25rem;
425
+ padding: 0 .25rem;
426
+ background-color: var(--cx-bg-main);
427
+ }
428
+
429
+ .cxd-search-shortcut-label {
430
+ padding-inline-start: .25rem;
431
+ @include font-size(var(--cx-font-size-small));
432
+ @include line-height(var(--cx-line-height-small));
433
+ }
418
434
  }