@sarimarcus/content-sites-ui 0.30.0 → 0.30.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All packages in the platform release in lockstep; entries are per release version.
4
4
 
5
+ ## 0.30.1 (2026-10-07)
6
+
7
+ - `./editorial`: `StaticProsePage` takes `proseClass`, the variant beside `prose` (default `prose-stone`) and `NotFoundPage` takes `searchBoxClass` (default: the current search-link classes), so a dark theme can pass its own prose variant and input tokens; default output is unchanged (SLA-2989).
8
+ - `./layout`: `ListingShell` takes `contained` (default `true`) and `pagefindIgnore` (default `true`); `false` drops the Container inside `<main>` for a main of full-bleed bands, and keeps a listing's `data-pagefind-body` bands indexed. `mainClass={null}` renders a class-less `<main>`. Default output is unchanged (SLA-2989).
9
+ - `./search`: with `?focus`, `initSearchPage` re-claims input focus every 300 ms for 3 s, stopping at the first pointer or key press, instead of once at 1.7 s and only from `<body>`; the deferred cookie banner moves focus to its own button, which the single reclaim missed. This reaches every site (SLA-2989).
10
+
5
11
  ## 0.30.0 (2026-10-07)
6
12
 
7
13
  - `./affiliate`: `HotelAffiliateCTA` and `AffiliateComparisonTable` links carry `data-track-affiliate-provider` and `data-track-affiliate-placement` so booking clicks are attributed; both take `provider`, `ComparisonRow` takes a per-row one, and without either the provider is read from the link's host (Booking.com links only; any other host is left unattributed) (SLA-2985).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarimarcus/content-sites-ui",
3
- "version": "0.30.0",
3
+ "version": "0.30.1",
4
4
  "description": "Universal Astro UI components, layout shell and design tokens shared by the content sites.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,6 +49,6 @@
49
49
  "@lucide/astro": "^1.23.0",
50
50
  "astro": "^7.0.6",
51
51
  "tailwindcss": "^4.1.18",
52
- "@sarimarcus/content-sites-core": "0.30.0"
52
+ "@sarimarcus/content-sites-core": "0.30.1"
53
53
  }
54
54
  }
@@ -20,9 +20,11 @@ interface Props {
20
20
  /** Accessible name of the section links. */
21
21
  exploreAriaLabel?: string;
22
22
  links: { href: string; label: string }[];
23
+ /** Classes of the search link; a dark theme passes its own border and background tokens. */
24
+ searchBoxClass?: string;
23
25
  }
24
26
 
25
- const { tagline, lede, homeHref, homeLabel, searchHref, searchLabel, searchAriaLabel, exploreLabel, exploreAriaLabel = 'Explore sections', links } = Astro.props;
27
+ const { tagline, lede, homeHref, homeLabel, searchHref, searchLabel, searchAriaLabel, exploreLabel, exploreAriaLabel = 'Explore sections', links, searchBoxClass = 'group flex items-center gap-3 w-full max-w-sm mx-auto px-4 py-3 rounded-xl border border-foreground/20 bg-white/60 backdrop-blur-sm text-left hover:border-primary/40 hover:bg-white/80 transition-all no-underline' } = Astro.props;
26
28
  ---
27
29
 
28
30
  <main class="relative min-h-[80vh] flex items-center justify-center overflow-hidden bg-muted">
@@ -74,7 +76,7 @@ const { tagline, lede, homeHref, homeLabel, searchHref, searchLabel, searchAriaL
74
76
  <a
75
77
  href={searchHref}
76
78
  data-search-trigger
77
- class="group flex items-center gap-3 w-full max-w-sm mx-auto px-4 py-3 rounded-xl border border-foreground/20 bg-white/60 backdrop-blur-sm text-left hover:border-primary/40 hover:bg-white/80 transition-all no-underline"
79
+ class={searchBoxClass}
78
80
  aria-label={searchAriaLabel}
79
81
  >
80
82
  <Icon name="search" class="w-4 h-4 text-muted-foreground shrink-0" />
@@ -18,9 +18,11 @@ interface Props {
18
18
  eyebrow?: string;
19
19
  /** Closing box under the prose: a short line and secondary buttons. */
20
20
  cta?: { text: string; links: CtaLink[] };
21
+ /** Prose variant beside the base `prose` class; a dark theme passes its own. */
22
+ proseClass?: string;
21
23
  }
22
24
 
23
- const { heading, eyebrow, cta } = Astro.props;
25
+ const { heading, eyebrow, cta, proseClass = 'prose-stone' } = Astro.props;
24
26
  ---
25
27
 
26
28
  <main class="py-16 lg:py-24">
@@ -28,7 +30,7 @@ const { heading, eyebrow, cta } = Astro.props;
28
30
  {eyebrow && <p class="text-sm font-semibold uppercase tracking-[0.18em] text-primary mb-3">{eyebrow}</p>}
29
31
  <h1 class="text-3xl sm:text-4xl font-medium font-display mb-8">{heading}</h1>
30
32
 
31
- <div class="prose prose-stone max-w-none">
33
+ <div class:list={['prose', proseClass, 'max-w-none']}>
32
34
  <slot />
33
35
  </div>
34
36
 
@@ -1,20 +1,26 @@
1
1
  ---
2
2
  /**
3
3
  * ListingShell — the frame of a listing hub: breadcrumb band, hero, optional full-width bands, <main> with a
4
- * Container, and trailing bands, all inside one Pagefind-ignore wrapper (listings are not indexed). The route keeps
4
+ * Container, and trailing bands, all inside one wrapper that is Pagefind-ignored by default (most listings are not
5
+ * indexed; `pagefindIgnore={false}` keeps a listing's own `data-pagefind-body` bands). The route keeps
5
6
  * Layout and passes StructuredData to Layout directly, so its JSON-LD lands in <head>; everything else comes in by slot.
6
7
  */
7
8
  import BreadcrumbBand from './BreadcrumbBand.astro';
8
9
  import Container from './Container.astro';
9
10
 
10
11
  interface Props {
11
- mainClass?: string;
12
+ /** `null` renders a class-less <main>. */
13
+ mainClass?: string | null;
14
+ /** `false` drops the Container inside <main>, for a main made of full-bleed bands that carry their own. */
15
+ contained?: boolean;
16
+ /** `false` keeps the page indexable, for a listing whose bands carry `data-pagefind-body` prose. */
17
+ pagefindIgnore?: boolean;
12
18
  }
13
19
 
14
- const { mainClass = 'py-12 lg:py-20' } = Astro.props;
20
+ const { mainClass = 'py-12 lg:py-20', contained = true, pagefindIgnore = true } = Astro.props;
15
21
  ---
16
22
 
17
- <div data-pagefind-ignore>
23
+ <div data-pagefind-ignore={pagefindIgnore ? '' : undefined}>
18
24
  <BreadcrumbBand ignore={false}>
19
25
  <slot name="breadcrumb" />
20
26
  </BreadcrumbBand>
@@ -23,9 +29,13 @@ const { mainClass = 'py-12 lg:py-20' } = Astro.props;
23
29
  <slot name="before-main" />
24
30
 
25
31
  <main class={mainClass}>
26
- <Container>
32
+ {contained ? (
33
+ <Container>
34
+ <slot />
35
+ </Container>
36
+ ) : (
27
37
  <slot />
28
- </Container>
38
+ )}
29
39
  </main>
30
40
 
31
41
  <slot name="after-main" />
@@ -591,12 +591,22 @@ export function initSearchPage<T extends string>(config: SearchPageConfig<T>): v
591
591
  }
592
592
  };
593
593
  requestAnimationFrame(claimFocus);
594
- // Cookie consent banner may steal focus ~1.5s after load (deferred init).
595
- // Reclaim once if the user hasn't moved focus elsewhere.
594
+ // The deferred cookie banner can steal focus (to its own button, not <body>) up to ~2s after load. Re-claim it
595
+ // every 300 ms for 3 s, and stop as soon as the user interacts.
596
596
  if (initial.focus) {
597
- window.setTimeout(() => {
598
- if (document.activeElement === document.body) claimFocus();
599
- }, 1700);
597
+ let tries = 0;
598
+ const stop = () => {
599
+ window.clearInterval(reclaim);
600
+ document.removeEventListener('pointerdown', stop, true);
601
+ document.removeEventListener('keydown', stop, true);
602
+ };
603
+ const reclaim = window.setInterval(() => {
604
+ tries += 1;
605
+ if (tries > 10) stop();
606
+ else claimFocus();
607
+ }, 300);
608
+ document.addEventListener('pointerdown', stop, true);
609
+ document.addEventListener('keydown', stop, true);
600
610
  }
601
611
  }
602
612
  }