@reblu/site-next 1.18.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +98 -9
- package/dist/cta-buttons.cjs +39 -12
- package/dist/cta-buttons.cjs.map +1 -1
- package/dist/cta-buttons.d.cts +34 -4
- package/dist/cta-buttons.d.ts +34 -4
- package/dist/cta-buttons.js +41 -14
- package/dist/cta-buttons.js.map +1 -1
- package/dist/index.cjs +41 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -9
- package/dist/index.d.ts +9 -9
- package/dist/index.js +45 -25
- package/dist/index.js.map +1 -1
- package/dist/seo.cjs +46 -0
- package/dist/seo.cjs.map +1 -1
- package/dist/seo.d.cts +43 -3
- package/dist/seo.d.ts +43 -3
- package/dist/seo.js +42 -0
- package/dist/seo.js.map +1 -1
- package/dist/shop-route-handlers.cjs.map +1 -1
- package/dist/shop-route-handlers.d.cts +10 -1
- package/dist/shop-route-handlers.d.ts +10 -1
- package/dist/shop-route-handlers.js.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# @reblu/site-next
|
|
2
2
|
|
|
3
|
+
## 1.20.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 92e0f6f: `CtaButtons` and `EditableCta` gain plug-and-play presentation props. The default markup is unchanged except for one accessibility fix: external CTAs now carry the new-window hint (see `newWindowHint`).
|
|
8
|
+
- `icon` — leading icon on every CTA, wrapped in an `aria-hidden` span.
|
|
9
|
+
- `primaryVariant` / `secondaryVariant` — `@reblu/site-ui` Button variant for the first CTA and for the rest (default `'default'`).
|
|
10
|
+
- `primaryClassName` / `secondaryClassName` — extra class per role, merged after `buttonClassName`, to map CTAs to the SITE's own button styles.
|
|
11
|
+
- `newWindowHint` — CTAs that open in a new tab now carry a visually-hidden «(se abre en una nueva ventana)» hint (a11y fix; overridable text, pass `''` to opt out, for example on a non-Spanish site). Their accessible name now ends with the hint.
|
|
12
|
+
|
|
13
|
+
`EditableCta` forwards every `CtaButtons` presentation prop and accepts the new `header` slot. New exported type: `CtaButtonVariant`.
|
|
14
|
+
|
|
15
|
+
- b75f066: Deprecate `createShopCheckEmailRoute` (REB-834). The proxied endpoint now always answers `{ exists: false }`, so do not mount it on a new SITE; an existing mount keeps working until the next major removes it. The README's "Wiring a SITE" no longer lists it.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [92e0f6f]
|
|
20
|
+
- Updated dependencies [b75f066]
|
|
21
|
+
- Updated dependencies [b75f066]
|
|
22
|
+
- @reblu/site-contracts@1.14.0
|
|
23
|
+
- @reblu/site-client@5.6.0
|
|
24
|
+
|
|
25
|
+
## 1.19.0
|
|
26
|
+
|
|
27
|
+
### Minor Changes
|
|
28
|
+
|
|
29
|
+
- fd17765: `@reblu/site-next/seo`: home / site-level metadata (REB-651). `buildMetadata({ config, home: true })` titles the home page with the site `seoTitle` as-is (never suffixed by the template; empty → `siteName`), describes it with `seoDescription` (empty → `siteDescription`), and turns a valid `canonicalUrl` into `metadataBase` plus `alternates.canonical`, so relative Open Graph paths resolve. New helpers: `resolveSiteSeoTitle`, `resolveSiteSeoDescription`, `validateCanonicalUrl` and `normalizeCanonicalUrl`.
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- Updated dependencies [fd17765]
|
|
34
|
+
- @reblu/site-contracts@1.12.0
|
|
35
|
+
|
|
3
36
|
## 1.18.0
|
|
4
37
|
|
|
5
38
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -80,6 +80,43 @@ import { client } from '@/lib/reblu'
|
|
|
80
80
|
<EditableCta slot="hero" client={client} fallback={<StaticHeroCta />} />
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
Slots: `header`, `hero`, `blog-end`, `services`, `contact`, `sticky-mobile`,
|
|
84
|
+
`footer`. A persistent header CTA, for example:
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { CalendarIcon } from 'lucide-react'
|
|
88
|
+
|
|
89
|
+
<EditableCta
|
|
90
|
+
slot="header"
|
|
91
|
+
client={client}
|
|
92
|
+
size="sm"
|
|
93
|
+
icon={<CalendarIcon />}
|
|
94
|
+
primaryVariant="default"
|
|
95
|
+
secondaryVariant="outline"
|
|
96
|
+
primaryClassName="btn-cta" // optional: map the primary CTA to your own button style
|
|
97
|
+
fallback={<StaticHeaderCta />}
|
|
98
|
+
/>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`EditableCta` forwards every `CtaButtons` presentation prop:
|
|
102
|
+
|
|
103
|
+
| Prop | Default | Effect |
|
|
104
|
+
| -------------------- | ---------------------------------- | ------------------------------------------------------- |
|
|
105
|
+
| `className` | — | Wrapper class (layout of the button group) |
|
|
106
|
+
| `buttonClassName` | — | Class on every button |
|
|
107
|
+
| `size` | `'lg'` | `@reblu/site-ui` Button size |
|
|
108
|
+
| `icon` | — | Leading icon on every CTA, wrapped in `aria-hidden` |
|
|
109
|
+
| `primaryVariant` | `'default'` | `@reblu/site-ui` Button variant of the first CTA |
|
|
110
|
+
| `secondaryVariant` | `'default'` | Button variant of every other CTA |
|
|
111
|
+
| `primaryClassName` | — | Extra class on the first CTA, after `buttonClassName` |
|
|
112
|
+
| `secondaryClassName` | — | Extra class on every other CTA, after `buttonClassName` |
|
|
113
|
+
| `newWindowHint` | `'(se abre en una nueva ventana)'` | Visually-hidden text on CTAs that open in a new tab |
|
|
114
|
+
|
|
115
|
+
CTAs with an absolute `http(s)` href open in a new tab
|
|
116
|
+
(`target="_blank" rel="noopener noreferrer"`) and carry the `newWindowHint` in a
|
|
117
|
+
`sr-only` span, so screen readers announce the new window. The primary CTA is the
|
|
118
|
+
first one by `sortOrder`.
|
|
119
|
+
|
|
83
120
|
`CtaButtons` (the `'use client'` renderer) and `StickyMobileCta` are also exported.
|
|
84
121
|
`CtaButtons` is additionally available at the `@reblu/site-next/cta-buttons`
|
|
85
122
|
subpath, which resolves directly to the emitted `'use client'` boundary.
|
|
@@ -325,7 +362,7 @@ its own Client Component.
|
|
|
325
362
|
| `createShopCheckoutRoute(opts?)` | `./shop-route-handlers` | No |
|
|
326
363
|
| `createShopValidateCartRoute()` | `./shop-route-handlers` | No |
|
|
327
364
|
| `createShopApplyDiscountRoute()` | `./shop-route-handlers` | No |
|
|
328
|
-
| `createShopCheckEmailRoute()` | `./shop-route-handlers` | No |
|
|
365
|
+
| `createShopCheckEmailRoute()` — **deprecated** (REB-834) | `./shop-route-handlers` | No |
|
|
329
366
|
| `createShopOrderRoute()` | `./shop-route-handlers` | No |
|
|
330
367
|
| `getShopOrderSummary(client, sessionId)` | `./shop-server` | No |
|
|
331
368
|
| `useShopOrderStatus(sessionId, opts?)` | `./shop` | **Yes** |
|
|
@@ -344,7 +381,7 @@ its own Client Component.
|
|
|
344
381
|
<CartProvider>{children}</CartProvider>
|
|
345
382
|
```
|
|
346
383
|
|
|
347
|
-
2. **Route Handlers** — mount the
|
|
384
|
+
2. **Route Handlers** — mount the four factories, one file each (mirrors the
|
|
348
385
|
`./route-handlers` auth pattern — a one-line file per route):
|
|
349
386
|
|
|
350
387
|
```ts
|
|
@@ -358,22 +395,37 @@ its own Client Component.
|
|
|
358
395
|
// app/api/shop/apply-discount/route.ts
|
|
359
396
|
export const POST = createShopApplyDiscountRoute()
|
|
360
397
|
|
|
361
|
-
// app/api/shop/check-email/route.ts
|
|
362
|
-
export const POST = createShopCheckEmailRoute()
|
|
363
|
-
|
|
364
398
|
// app/api/shop/order/[sessionId]/route.ts
|
|
365
399
|
export const GET = createShopOrderRoute()
|
|
366
400
|
```
|
|
367
401
|
|
|
368
|
-
All
|
|
369
|
-
(same as `./route-handlers`'s `createProxyRoute`, which they
|
|
370
|
-
API key never reaches the browser.
|
|
402
|
+
All of them read `REBLU_API_BASE_URL`/`REBLU_API_KEY` lazily from
|
|
403
|
+
`process.env` (same as `./route-handlers`'s `createProxyRoute`, which they
|
|
404
|
+
wrap) — the API key never reaches the browser.
|
|
405
|
+
|
|
406
|
+
Do not mount `createShopCheckEmailRoute()` on a new SITE: it is
|
|
407
|
+
**deprecated** (REB-834) and removed in the next major. Its endpoint used to
|
|
408
|
+
say whether an email had an account — an enumeration oracle on the
|
|
409
|
+
tenant's customer list — and now always answers `{ exists: false }`. An
|
|
410
|
+
existing mount keeps working; remove it at your convenience. The checkout
|
|
411
|
+
always offers «Iniciar sesión o continuar como invitado», and the server
|
|
412
|
+
links a guest's order to an existing account after payment.
|
|
413
|
+
|
|
414
|
+
**Forward the visitor's IP.** base-api rate-limits some public routes
|
|
415
|
+
per client IP (check-email, and the auth routes such as login and
|
|
416
|
+
magic-link), reading the IP from `x-forwarded-for`. A SITE
|
|
417
|
+
that calls `/api/site/*` from its own server must forward the visitor's
|
|
418
|
+
`x-forwarded-for` (and `x-real-ip`), or every visitor shares the SITE
|
|
419
|
+
server's single IP window and one busy page throttles them all.
|
|
420
|
+
`createProxyRoute` — and therefore every `createShop*Route` factory —
|
|
421
|
+
already forwards both headers; only hand-written server-side calls need to
|
|
422
|
+
do it themselves.
|
|
371
423
|
|
|
372
424
|
3. **Pages** — create `app/shop/cart/page.tsx`, `app/shop/checkout/page.tsx`
|
|
373
425
|
and `app/shop/order-confirmation/page.tsx`. Read the cart with `useCart()`
|
|
374
426
|
from `./cart`, render `@reblu/site-blocks`'s `CartLineList`/`CartSummary`/
|
|
375
427
|
`CheckoutForm`/`OrderConfirmation`, and `fetch` the Route Handlers from
|
|
376
|
-
step 2 for checkout/validate-cart/apply-discount/
|
|
428
|
+
step 2 for checkout/validate-cart/apply-discount/order (never
|
|
377
429
|
call base-api directly from a Client Component — that's what step 2's
|
|
378
430
|
Route Handlers are for). See `apps/demo-site`'s `shop/{cart,checkout,
|
|
379
431
|
order-confirmation}/page.tsx` in this monorepo for a complete, working
|
|
@@ -682,6 +734,43 @@ yet. Need the string only? `resolveSeoTitle({ seoTitle, title, sectionLabel },
|
|
|
682
734
|
{ template: config.seoTitleTemplate, siteName: config.siteName })`.
|
|
683
735
|
Description/Open Graph are intentionally minimal here (REB-823 extends them).
|
|
684
736
|
|
|
737
|
+
### Home page and canonical URL — `buildMetadata({ config, home: true })` (REB-651)
|
|
738
|
+
|
|
739
|
+
The site has its own search title and description in nexus (Ajustes → SEO y
|
|
740
|
+
Redes), separate from `siteName`/`siteDescription`, which stay the identity
|
|
741
|
+
used in breadcrumbs and chrome. `GET /api/site/config` serves them as
|
|
742
|
+
`seoTitle`, `seoDescription` and `canonicalUrl` (all `null` when unset).
|
|
743
|
+
|
|
744
|
+
```ts
|
|
745
|
+
// app/page.tsx — the home page: no hardcoded title, description or domain
|
|
746
|
+
import type { Metadata } from 'next'
|
|
747
|
+
import { buildMetadata } from '@reblu/site-next/seo'
|
|
748
|
+
|
|
749
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
750
|
+
return buildMetadata({ config: await reblu.config.get(), home: true })
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// app/layout.tsx — every route resolves relative Open Graph paths against it
|
|
754
|
+
import { normalizeCanonicalUrl } from '@reblu/site-next/seo'
|
|
755
|
+
|
|
756
|
+
export async function generateMetadata(): Promise<Metadata> {
|
|
757
|
+
const config = await reblu.config.get()
|
|
758
|
+
const siteUrl = normalizeCanonicalUrl(config.canonicalUrl) ?? process.env.NEXT_PUBLIC_SITE_URL!
|
|
759
|
+
return { metadataBase: new URL(siteUrl) /* , title, openGraph… */ }
|
|
760
|
+
}
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
- `config.seoTitle`, when set, is the **whole** home title — used as-is, never
|
|
764
|
+
suffixed by the template. Empty → `siteName`, exactly as before.
|
|
765
|
+
- `config.seoDescription` falls back to `siteDescription`.
|
|
766
|
+
- A valid `config.canonicalUrl` (an `https` origin) becomes `metadataBase`
|
|
767
|
+
plus `alternates.canonical: '/'`, so an `ogImageUrl` given as a relative
|
|
768
|
+
path resolves against the site's domain. The env var becomes a fallback.
|
|
769
|
+
|
|
770
|
+
`home: true` is explicit: a route whose title happens to be empty never picks
|
|
771
|
+
up the site's SEO title. Need the strings only? `resolveSiteSeoTitle(config)`
|
|
772
|
+
and `resolveSiteSeoDescription(config)`.
|
|
773
|
+
|
|
685
774
|
## Structured data (JSON-LD)
|
|
686
775
|
|
|
687
776
|
`@reblu/site-next/json-ld` (REB-622, server-only, dependency-free) renders one
|
package/dist/cta-buttons.cjs
CHANGED
|
@@ -37,22 +37,49 @@ module.exports = __toCommonJS(cta_buttons_exports);
|
|
|
37
37
|
var import_site_ui = require("@reblu/site-ui");
|
|
38
38
|
var import_link = __toESM(require("next/link"), 1);
|
|
39
39
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
40
|
+
var DEFAULT_NEW_WINDOW_HINT = "(se abre en una nueva ventana)";
|
|
40
41
|
function isExternal(href) {
|
|
41
42
|
return /^https?:\/\//i.test(href);
|
|
42
43
|
}
|
|
43
|
-
function CtaButtons({
|
|
44
|
+
function CtaButtons({
|
|
45
|
+
ctas,
|
|
46
|
+
className,
|
|
47
|
+
buttonClassName,
|
|
48
|
+
size = "lg",
|
|
49
|
+
icon,
|
|
50
|
+
primaryVariant = "default",
|
|
51
|
+
secondaryVariant = "default",
|
|
52
|
+
primaryClassName,
|
|
53
|
+
secondaryClassName,
|
|
54
|
+
newWindowHint = DEFAULT_NEW_WINDOW_HINT
|
|
55
|
+
}) {
|
|
44
56
|
if (ctas.length === 0) return null;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
const leadingIcon = icon == null ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", className: "inline-flex shrink-0", children: icon });
|
|
58
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, "data-slot": "cta-buttons", children: ctas.map((cta, index) => {
|
|
59
|
+
const isPrimary = index === 0;
|
|
60
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
61
|
+
import_site_ui.Button,
|
|
62
|
+
{
|
|
63
|
+
asChild: true,
|
|
64
|
+
size,
|
|
65
|
+
variant: isPrimary ? primaryVariant : secondaryVariant,
|
|
66
|
+
className: (0, import_site_ui.cn)(buttonClassName, isPrimary ? primaryClassName : secondaryClassName),
|
|
67
|
+
"data-cta-id": cta.analyticsId ?? void 0,
|
|
68
|
+
children: isExternal(cta.href) ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: cta.href, target: "_blank", rel: "noopener noreferrer", children: [
|
|
69
|
+
leadingIcon,
|
|
70
|
+
cta.label,
|
|
71
|
+
newWindowHint ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
72
|
+
" ",
|
|
73
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sr-only", children: newWindowHint })
|
|
74
|
+
] }) : null
|
|
75
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href: cta.href, children: [
|
|
76
|
+
leadingIcon,
|
|
77
|
+
cta.label
|
|
78
|
+
] })
|
|
79
|
+
},
|
|
80
|
+
cta.id
|
|
81
|
+
);
|
|
82
|
+
}) });
|
|
56
83
|
}
|
|
57
84
|
// Annotate the CommonJS export names for ESM import in node:
|
|
58
85
|
0 && (module.exports = {
|
package/dist/cta-buttons.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cta-buttons.tsx"],"sourcesContent":["'use client'\n\nimport type { Cta } from '@reblu/site-contracts'\nimport { Button } from '@reblu/site-ui'\nimport Link from 'next/link'\n\ntype ButtonSize = 'sm' | 'default' | 'lg'\n\nexport interface CtaButtonsProps {\n ctas: Cta[]\n /** Wrapper class (layout of the button group). */\n className?: string\n /** Class applied to each button. */\n buttonClassName?: string\n size?: ButtonSize\n}\n\n/** External = absolute http(s) URL (e.g. a
|
|
1
|
+
{"version":3,"sources":["../src/cta-buttons.tsx"],"sourcesContent":["'use client'\n\nimport type { Cta } from '@reblu/site-contracts'\nimport { Button, cn, type ButtonVariantProps } from '@reblu/site-ui'\nimport Link from 'next/link'\nimport type { ReactNode } from 'react'\n\ntype ButtonSize = 'sm' | 'default' | 'lg'\n\n/** A `@reblu/site-ui` Button variant (`default`, `outline`, `secondary`, …). */\nexport type CtaButtonVariant = NonNullable<ButtonVariantProps['variant']>\n\n/** Default screen-reader hint appended to CTAs that open in a new tab. */\nconst DEFAULT_NEW_WINDOW_HINT = '(se abre en una nueva ventana)'\n\nexport interface CtaButtonsProps {\n ctas: Cta[]\n /** Wrapper class (layout of the button group). */\n className?: string\n /** Class applied to each button. */\n buttonClassName?: string\n size?: ButtonSize\n /**\n * Leading icon rendered before every CTA label. Decorative: it is wrapped in\n * an `aria-hidden` span, so the accessible name stays the label.\n */\n icon?: ReactNode\n /** Button variant of the primary CTA (the first one, by `sortOrder`). Default `default`. */\n primaryVariant?: CtaButtonVariant\n /** Button variant of every secondary CTA (all but the first). Default `default`. */\n secondaryVariant?: CtaButtonVariant\n /**\n * Extra class for the primary CTA, merged after `buttonClassName`. Lets a SITE\n * map the primary CTA to its own button style (e.g. a brand `cta` variant).\n */\n primaryClassName?: string\n /** Extra class for every secondary CTA, merged after `buttonClassName`. */\n secondaryClassName?: string\n /**\n * Visually-hidden text appended to CTAs that open in a new tab, so screen\n * readers announce the context change. Default «(se abre en una nueva ventana)».\n */\n newWindowHint?: string\n}\n\n/** External = absolute http(s) URL (e.g. a third-party booking link). */\nfunction isExternal(href: string): boolean {\n return /^https?:\\/\\//i.test(href)\n}\n\n/**\n * Presentational CTA renderer (REB-394, moved to `@reblu/site-next` in REB-418).\n * Pure — takes already-resolved CTAs and renders one styled button-link each.\n * Internal hrefs use `next/link` (client routing); external URLs open in a new,\n * safely-rel tab and carry a visually-hidden new-window hint. `analyticsId` is\n * surfaced as `data-cta-id` on the clickable element for GA `cta_click`\n * tracking. Renders nothing when there are no CTAs.\n *\n * The first CTA is the primary one; the rest are secondary. Each role takes its\n * own Button variant and extra class, so a SITE maps the CTAs to its own button\n * styles without forking this markup.\n *\n * Imports `Button` from `@reblu/site-ui` (public) — never `@reblu/ui` (private).\n */\nexport function CtaButtons({\n ctas,\n className,\n buttonClassName,\n size = 'lg',\n icon,\n primaryVariant = 'default',\n secondaryVariant = 'default',\n primaryClassName,\n secondaryClassName,\n newWindowHint = DEFAULT_NEW_WINDOW_HINT,\n}: CtaButtonsProps) {\n if (ctas.length === 0) return null\n\n const leadingIcon =\n icon == null ? null : (\n <span aria-hidden='true' className='inline-flex shrink-0'>\n {icon}\n </span>\n )\n\n return (\n <div className={className} data-slot='cta-buttons'>\n {ctas.map((cta, index) => {\n const isPrimary = index === 0\n return (\n <Button\n key={cta.id}\n asChild\n size={size}\n variant={isPrimary ? primaryVariant : secondaryVariant}\n className={cn(buttonClassName, isPrimary ? primaryClassName : secondaryClassName)}\n data-cta-id={cta.analyticsId ?? undefined}\n >\n {isExternal(cta.href) ? (\n <a href={cta.href} target='_blank' rel='noopener noreferrer'>\n {leadingIcon}\n {cta.label}\n {newWindowHint ? <> <span className='sr-only'>{newWindowHint}</span></> : null}\n </a>\n ) : (\n <Link href={cta.href}>\n {leadingIcon}\n {cta.label}\n </Link>\n )}\n </Button>\n )\n })}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,qBAAoD;AACpD,kBAAiB;AA4EX;AAnEN,IAAM,0BAA0B;AAiChC,SAAS,WAAW,MAAuB;AACzC,SAAO,gBAAgB,KAAK,IAAI;AAClC;AAgBO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB;AAAA,EACA;AAAA,EACA,gBAAgB;AAClB,GAAoB;AAClB,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QAAM,cACJ,QAAQ,OAAO,OACb,4CAAC,UAAK,eAAY,QAAO,WAAU,wBAChC,gBACH;AAGJ,SACE,4CAAC,SAAI,WAAsB,aAAU,eAClC,eAAK,IAAI,CAAC,KAAK,UAAU;AACxB,UAAM,YAAY,UAAU;AAC5B,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,SAAO;AAAA,QACP;AAAA,QACA,SAAS,YAAY,iBAAiB;AAAA,QACtC,eAAW,mBAAG,iBAAiB,YAAY,mBAAmB,kBAAkB;AAAA,QAChF,eAAa,IAAI,eAAe;AAAA,QAE/B,qBAAW,IAAI,IAAI,IAClB,6CAAC,OAAE,MAAM,IAAI,MAAM,QAAO,UAAS,KAAI,uBACpC;AAAA;AAAA,UACA,IAAI;AAAA,UACJ,gBAAgB,4EAAE;AAAA;AAAA,YAAC,4CAAC,UAAK,WAAU,WAAW,yBAAc;AAAA,aAAO,IAAM;AAAA,WAC5E,IAEA,6CAAC,YAAAA,SAAA,EAAK,MAAM,IAAI,MACb;AAAA;AAAA,UACA,IAAI;AAAA,WACP;AAAA;AAAA,MAjBG,IAAI;AAAA,IAmBX;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":["Link"]}
|
package/dist/cta-buttons.d.cts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { Cta } from '@reblu/site-contracts';
|
|
3
|
+
import { ButtonVariantProps } from '@reblu/site-ui';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
3
5
|
|
|
4
6
|
type ButtonSize = 'sm' | 'default' | 'lg';
|
|
7
|
+
/** A `@reblu/site-ui` Button variant (`default`, `outline`, `secondary`, …). */
|
|
8
|
+
type CtaButtonVariant = NonNullable<ButtonVariantProps['variant']>;
|
|
5
9
|
interface CtaButtonsProps {
|
|
6
10
|
ctas: Cta[];
|
|
7
11
|
/** Wrapper class (layout of the button group). */
|
|
@@ -9,16 +13,42 @@ interface CtaButtonsProps {
|
|
|
9
13
|
/** Class applied to each button. */
|
|
10
14
|
buttonClassName?: string;
|
|
11
15
|
size?: ButtonSize;
|
|
16
|
+
/**
|
|
17
|
+
* Leading icon rendered before every CTA label. Decorative: it is wrapped in
|
|
18
|
+
* an `aria-hidden` span, so the accessible name stays the label.
|
|
19
|
+
*/
|
|
20
|
+
icon?: ReactNode;
|
|
21
|
+
/** Button variant of the primary CTA (the first one, by `sortOrder`). Default `default`. */
|
|
22
|
+
primaryVariant?: CtaButtonVariant;
|
|
23
|
+
/** Button variant of every secondary CTA (all but the first). Default `default`. */
|
|
24
|
+
secondaryVariant?: CtaButtonVariant;
|
|
25
|
+
/**
|
|
26
|
+
* Extra class for the primary CTA, merged after `buttonClassName`. Lets a SITE
|
|
27
|
+
* map the primary CTA to its own button style (e.g. a brand `cta` variant).
|
|
28
|
+
*/
|
|
29
|
+
primaryClassName?: string;
|
|
30
|
+
/** Extra class for every secondary CTA, merged after `buttonClassName`. */
|
|
31
|
+
secondaryClassName?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Visually-hidden text appended to CTAs that open in a new tab, so screen
|
|
34
|
+
* readers announce the context change. Default «(se abre en una nueva ventana)».
|
|
35
|
+
*/
|
|
36
|
+
newWindowHint?: string;
|
|
12
37
|
}
|
|
13
38
|
/**
|
|
14
39
|
* Presentational CTA renderer (REB-394, moved to `@reblu/site-next` in REB-418).
|
|
15
40
|
* Pure — takes already-resolved CTAs and renders one styled button-link each.
|
|
16
41
|
* Internal hrefs use `next/link` (client routing); external URLs open in a new,
|
|
17
|
-
* safely-rel tab
|
|
18
|
-
*
|
|
42
|
+
* safely-rel tab and carry a visually-hidden new-window hint. `analyticsId` is
|
|
43
|
+
* surfaced as `data-cta-id` on the clickable element for GA `cta_click`
|
|
44
|
+
* tracking. Renders nothing when there are no CTAs.
|
|
45
|
+
*
|
|
46
|
+
* The first CTA is the primary one; the rest are secondary. Each role takes its
|
|
47
|
+
* own Button variant and extra class, so a SITE maps the CTAs to its own button
|
|
48
|
+
* styles without forking this markup.
|
|
19
49
|
*
|
|
20
50
|
* Imports `Button` from `@reblu/site-ui` (public) — never `@reblu/ui` (private).
|
|
21
51
|
*/
|
|
22
|
-
declare function CtaButtons({ ctas, className, buttonClassName, size }: CtaButtonsProps): react_jsx_runtime.JSX.Element | null;
|
|
52
|
+
declare function CtaButtons({ ctas, className, buttonClassName, size, icon, primaryVariant, secondaryVariant, primaryClassName, secondaryClassName, newWindowHint, }: CtaButtonsProps): react_jsx_runtime.JSX.Element | null;
|
|
23
53
|
|
|
24
|
-
export { CtaButtons, type CtaButtonsProps };
|
|
54
|
+
export { type CtaButtonVariant, CtaButtons, type CtaButtonsProps };
|
package/dist/cta-buttons.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { Cta } from '@reblu/site-contracts';
|
|
3
|
+
import { ButtonVariantProps } from '@reblu/site-ui';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
3
5
|
|
|
4
6
|
type ButtonSize = 'sm' | 'default' | 'lg';
|
|
7
|
+
/** A `@reblu/site-ui` Button variant (`default`, `outline`, `secondary`, …). */
|
|
8
|
+
type CtaButtonVariant = NonNullable<ButtonVariantProps['variant']>;
|
|
5
9
|
interface CtaButtonsProps {
|
|
6
10
|
ctas: Cta[];
|
|
7
11
|
/** Wrapper class (layout of the button group). */
|
|
@@ -9,16 +13,42 @@ interface CtaButtonsProps {
|
|
|
9
13
|
/** Class applied to each button. */
|
|
10
14
|
buttonClassName?: string;
|
|
11
15
|
size?: ButtonSize;
|
|
16
|
+
/**
|
|
17
|
+
* Leading icon rendered before every CTA label. Decorative: it is wrapped in
|
|
18
|
+
* an `aria-hidden` span, so the accessible name stays the label.
|
|
19
|
+
*/
|
|
20
|
+
icon?: ReactNode;
|
|
21
|
+
/** Button variant of the primary CTA (the first one, by `sortOrder`). Default `default`. */
|
|
22
|
+
primaryVariant?: CtaButtonVariant;
|
|
23
|
+
/** Button variant of every secondary CTA (all but the first). Default `default`. */
|
|
24
|
+
secondaryVariant?: CtaButtonVariant;
|
|
25
|
+
/**
|
|
26
|
+
* Extra class for the primary CTA, merged after `buttonClassName`. Lets a SITE
|
|
27
|
+
* map the primary CTA to its own button style (e.g. a brand `cta` variant).
|
|
28
|
+
*/
|
|
29
|
+
primaryClassName?: string;
|
|
30
|
+
/** Extra class for every secondary CTA, merged after `buttonClassName`. */
|
|
31
|
+
secondaryClassName?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Visually-hidden text appended to CTAs that open in a new tab, so screen
|
|
34
|
+
* readers announce the context change. Default «(se abre en una nueva ventana)».
|
|
35
|
+
*/
|
|
36
|
+
newWindowHint?: string;
|
|
12
37
|
}
|
|
13
38
|
/**
|
|
14
39
|
* Presentational CTA renderer (REB-394, moved to `@reblu/site-next` in REB-418).
|
|
15
40
|
* Pure — takes already-resolved CTAs and renders one styled button-link each.
|
|
16
41
|
* Internal hrefs use `next/link` (client routing); external URLs open in a new,
|
|
17
|
-
* safely-rel tab
|
|
18
|
-
*
|
|
42
|
+
* safely-rel tab and carry a visually-hidden new-window hint. `analyticsId` is
|
|
43
|
+
* surfaced as `data-cta-id` on the clickable element for GA `cta_click`
|
|
44
|
+
* tracking. Renders nothing when there are no CTAs.
|
|
45
|
+
*
|
|
46
|
+
* The first CTA is the primary one; the rest are secondary. Each role takes its
|
|
47
|
+
* own Button variant and extra class, so a SITE maps the CTAs to its own button
|
|
48
|
+
* styles without forking this markup.
|
|
19
49
|
*
|
|
20
50
|
* Imports `Button` from `@reblu/site-ui` (public) — never `@reblu/ui` (private).
|
|
21
51
|
*/
|
|
22
|
-
declare function CtaButtons({ ctas, className, buttonClassName, size }: CtaButtonsProps): react_jsx_runtime.JSX.Element | null;
|
|
52
|
+
declare function CtaButtons({ ctas, className, buttonClassName, size, icon, primaryVariant, secondaryVariant, primaryClassName, secondaryClassName, newWindowHint, }: CtaButtonsProps): react_jsx_runtime.JSX.Element | null;
|
|
23
53
|
|
|
24
|
-
export { CtaButtons, type CtaButtonsProps };
|
|
54
|
+
export { type CtaButtonVariant, CtaButtons, type CtaButtonsProps };
|
package/dist/cta-buttons.js
CHANGED
|
@@ -1,25 +1,52 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/cta-buttons.tsx
|
|
4
|
-
import { Button } from "@reblu/site-ui";
|
|
4
|
+
import { Button, cn } from "@reblu/site-ui";
|
|
5
5
|
import Link from "next/link";
|
|
6
|
-
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
var DEFAULT_NEW_WINDOW_HINT = "(se abre en una nueva ventana)";
|
|
7
8
|
function isExternal(href) {
|
|
8
9
|
return /^https?:\/\//i.test(href);
|
|
9
10
|
}
|
|
10
|
-
function CtaButtons({
|
|
11
|
+
function CtaButtons({
|
|
12
|
+
ctas,
|
|
13
|
+
className,
|
|
14
|
+
buttonClassName,
|
|
15
|
+
size = "lg",
|
|
16
|
+
icon,
|
|
17
|
+
primaryVariant = "default",
|
|
18
|
+
secondaryVariant = "default",
|
|
19
|
+
primaryClassName,
|
|
20
|
+
secondaryClassName,
|
|
21
|
+
newWindowHint = DEFAULT_NEW_WINDOW_HINT
|
|
22
|
+
}) {
|
|
11
23
|
if (ctas.length === 0) return null;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
const leadingIcon = icon == null ? null : /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "inline-flex shrink-0", children: icon });
|
|
25
|
+
return /* @__PURE__ */ jsx("div", { className, "data-slot": "cta-buttons", children: ctas.map((cta, index) => {
|
|
26
|
+
const isPrimary = index === 0;
|
|
27
|
+
return /* @__PURE__ */ jsx(
|
|
28
|
+
Button,
|
|
29
|
+
{
|
|
30
|
+
asChild: true,
|
|
31
|
+
size,
|
|
32
|
+
variant: isPrimary ? primaryVariant : secondaryVariant,
|
|
33
|
+
className: cn(buttonClassName, isPrimary ? primaryClassName : secondaryClassName),
|
|
34
|
+
"data-cta-id": cta.analyticsId ?? void 0,
|
|
35
|
+
children: isExternal(cta.href) ? /* @__PURE__ */ jsxs("a", { href: cta.href, target: "_blank", rel: "noopener noreferrer", children: [
|
|
36
|
+
leadingIcon,
|
|
37
|
+
cta.label,
|
|
38
|
+
newWindowHint ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
39
|
+
" ",
|
|
40
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: newWindowHint })
|
|
41
|
+
] }) : null
|
|
42
|
+
] }) : /* @__PURE__ */ jsxs(Link, { href: cta.href, children: [
|
|
43
|
+
leadingIcon,
|
|
44
|
+
cta.label
|
|
45
|
+
] })
|
|
46
|
+
},
|
|
47
|
+
cta.id
|
|
48
|
+
);
|
|
49
|
+
}) });
|
|
23
50
|
}
|
|
24
51
|
export {
|
|
25
52
|
CtaButtons
|
package/dist/cta-buttons.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cta-buttons.tsx"],"sourcesContent":["'use client'\n\nimport type { Cta } from '@reblu/site-contracts'\nimport { Button } from '@reblu/site-ui'\nimport Link from 'next/link'\n\ntype ButtonSize = 'sm' | 'default' | 'lg'\n\nexport interface CtaButtonsProps {\n ctas: Cta[]\n /** Wrapper class (layout of the button group). */\n className?: string\n /** Class applied to each button. */\n buttonClassName?: string\n size?: ButtonSize\n}\n\n/** External = absolute http(s) URL (e.g. a
|
|
1
|
+
{"version":3,"sources":["../src/cta-buttons.tsx"],"sourcesContent":["'use client'\n\nimport type { Cta } from '@reblu/site-contracts'\nimport { Button, cn, type ButtonVariantProps } from '@reblu/site-ui'\nimport Link from 'next/link'\nimport type { ReactNode } from 'react'\n\ntype ButtonSize = 'sm' | 'default' | 'lg'\n\n/** A `@reblu/site-ui` Button variant (`default`, `outline`, `secondary`, …). */\nexport type CtaButtonVariant = NonNullable<ButtonVariantProps['variant']>\n\n/** Default screen-reader hint appended to CTAs that open in a new tab. */\nconst DEFAULT_NEW_WINDOW_HINT = '(se abre en una nueva ventana)'\n\nexport interface CtaButtonsProps {\n ctas: Cta[]\n /** Wrapper class (layout of the button group). */\n className?: string\n /** Class applied to each button. */\n buttonClassName?: string\n size?: ButtonSize\n /**\n * Leading icon rendered before every CTA label. Decorative: it is wrapped in\n * an `aria-hidden` span, so the accessible name stays the label.\n */\n icon?: ReactNode\n /** Button variant of the primary CTA (the first one, by `sortOrder`). Default `default`. */\n primaryVariant?: CtaButtonVariant\n /** Button variant of every secondary CTA (all but the first). Default `default`. */\n secondaryVariant?: CtaButtonVariant\n /**\n * Extra class for the primary CTA, merged after `buttonClassName`. Lets a SITE\n * map the primary CTA to its own button style (e.g. a brand `cta` variant).\n */\n primaryClassName?: string\n /** Extra class for every secondary CTA, merged after `buttonClassName`. */\n secondaryClassName?: string\n /**\n * Visually-hidden text appended to CTAs that open in a new tab, so screen\n * readers announce the context change. Default «(se abre en una nueva ventana)».\n */\n newWindowHint?: string\n}\n\n/** External = absolute http(s) URL (e.g. a third-party booking link). */\nfunction isExternal(href: string): boolean {\n return /^https?:\\/\\//i.test(href)\n}\n\n/**\n * Presentational CTA renderer (REB-394, moved to `@reblu/site-next` in REB-418).\n * Pure — takes already-resolved CTAs and renders one styled button-link each.\n * Internal hrefs use `next/link` (client routing); external URLs open in a new,\n * safely-rel tab and carry a visually-hidden new-window hint. `analyticsId` is\n * surfaced as `data-cta-id` on the clickable element for GA `cta_click`\n * tracking. Renders nothing when there are no CTAs.\n *\n * The first CTA is the primary one; the rest are secondary. Each role takes its\n * own Button variant and extra class, so a SITE maps the CTAs to its own button\n * styles without forking this markup.\n *\n * Imports `Button` from `@reblu/site-ui` (public) — never `@reblu/ui` (private).\n */\nexport function CtaButtons({\n ctas,\n className,\n buttonClassName,\n size = 'lg',\n icon,\n primaryVariant = 'default',\n secondaryVariant = 'default',\n primaryClassName,\n secondaryClassName,\n newWindowHint = DEFAULT_NEW_WINDOW_HINT,\n}: CtaButtonsProps) {\n if (ctas.length === 0) return null\n\n const leadingIcon =\n icon == null ? null : (\n <span aria-hidden='true' className='inline-flex shrink-0'>\n {icon}\n </span>\n )\n\n return (\n <div className={className} data-slot='cta-buttons'>\n {ctas.map((cta, index) => {\n const isPrimary = index === 0\n return (\n <Button\n key={cta.id}\n asChild\n size={size}\n variant={isPrimary ? primaryVariant : secondaryVariant}\n className={cn(buttonClassName, isPrimary ? primaryClassName : secondaryClassName)}\n data-cta-id={cta.analyticsId ?? undefined}\n >\n {isExternal(cta.href) ? (\n <a href={cta.href} target='_blank' rel='noopener noreferrer'>\n {leadingIcon}\n {cta.label}\n {newWindowHint ? <> <span className='sr-only'>{newWindowHint}</span></> : null}\n </a>\n ) : (\n <Link href={cta.href}>\n {leadingIcon}\n {cta.label}\n </Link>\n )}\n </Button>\n )\n })}\n </div>\n )\n}\n"],"mappings":";;;AAGA,SAAS,QAAQ,UAAmC;AACpD,OAAO,UAAU;AA4EX,SAsB2B,UAtB3B,KAsB2B,YAtB3B;AAnEN,IAAM,0BAA0B;AAiChC,SAAS,WAAW,MAAuB;AACzC,SAAO,gBAAgB,KAAK,IAAI;AAClC;AAgBO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB;AAAA,EACA;AAAA,EACA,gBAAgB;AAClB,GAAoB;AAClB,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QAAM,cACJ,QAAQ,OAAO,OACb,oBAAC,UAAK,eAAY,QAAO,WAAU,wBAChC,gBACH;AAGJ,SACE,oBAAC,SAAI,WAAsB,aAAU,eAClC,eAAK,IAAI,CAAC,KAAK,UAAU;AACxB,UAAM,YAAY,UAAU;AAC5B,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,SAAO;AAAA,QACP;AAAA,QACA,SAAS,YAAY,iBAAiB;AAAA,QACtC,WAAW,GAAG,iBAAiB,YAAY,mBAAmB,kBAAkB;AAAA,QAChF,eAAa,IAAI,eAAe;AAAA,QAE/B,qBAAW,IAAI,IAAI,IAClB,qBAAC,OAAE,MAAM,IAAI,MAAM,QAAO,UAAS,KAAI,uBACpC;AAAA;AAAA,UACA,IAAI;AAAA,UACJ,gBAAgB,iCAAE;AAAA;AAAA,YAAC,oBAAC,UAAK,WAAU,WAAW,yBAAc;AAAA,aAAO,IAAM;AAAA,WAC5E,IAEA,qBAAC,QAAK,MAAM,IAAI,MACb;AAAA;AAAA,UACA,IAAI;AAAA,WACP;AAAA;AAAA,MAjBG,IAAI;AAAA,IAmBX;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -61,22 +61,49 @@ var NextCacheAdapter = class {
|
|
|
61
61
|
var import_site_ui = require("@reblu/site-ui");
|
|
62
62
|
var import_link = __toESM(require("next/link"), 1);
|
|
63
63
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
64
|
+
var DEFAULT_NEW_WINDOW_HINT = "(se abre en una nueva ventana)";
|
|
64
65
|
function isExternal(href) {
|
|
65
66
|
return /^https?:\/\//i.test(href);
|
|
66
67
|
}
|
|
67
|
-
function CtaButtons({
|
|
68
|
+
function CtaButtons({
|
|
69
|
+
ctas,
|
|
70
|
+
className,
|
|
71
|
+
buttonClassName,
|
|
72
|
+
size = "lg",
|
|
73
|
+
icon,
|
|
74
|
+
primaryVariant = "default",
|
|
75
|
+
secondaryVariant = "default",
|
|
76
|
+
primaryClassName,
|
|
77
|
+
secondaryClassName,
|
|
78
|
+
newWindowHint = DEFAULT_NEW_WINDOW_HINT
|
|
79
|
+
}) {
|
|
68
80
|
if (ctas.length === 0) return null;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
const leadingIcon = icon == null ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", className: "inline-flex shrink-0", children: icon });
|
|
82
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, "data-slot": "cta-buttons", children: ctas.map((cta, index) => {
|
|
83
|
+
const isPrimary = index === 0;
|
|
84
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
85
|
+
import_site_ui.Button,
|
|
86
|
+
{
|
|
87
|
+
asChild: true,
|
|
88
|
+
size,
|
|
89
|
+
variant: isPrimary ? primaryVariant : secondaryVariant,
|
|
90
|
+
className: (0, import_site_ui.cn)(buttonClassName, isPrimary ? primaryClassName : secondaryClassName),
|
|
91
|
+
"data-cta-id": cta.analyticsId ?? void 0,
|
|
92
|
+
children: isExternal(cta.href) ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: cta.href, target: "_blank", rel: "noopener noreferrer", children: [
|
|
93
|
+
leadingIcon,
|
|
94
|
+
cta.label,
|
|
95
|
+
newWindowHint ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
96
|
+
" ",
|
|
97
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sr-only", children: newWindowHint })
|
|
98
|
+
] }) : null
|
|
99
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href: cta.href, children: [
|
|
100
|
+
leadingIcon,
|
|
101
|
+
cta.label
|
|
102
|
+
] })
|
|
103
|
+
},
|
|
104
|
+
cta.id
|
|
105
|
+
);
|
|
106
|
+
}) });
|
|
80
107
|
}
|
|
81
108
|
|
|
82
109
|
// src/sticky-mobile-cta.tsx
|
|
@@ -96,17 +123,10 @@ function StickyMobileCta({ ctas }) {
|
|
|
96
123
|
|
|
97
124
|
// src/editable-cta.tsx
|
|
98
125
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
99
|
-
async function EditableCta({
|
|
100
|
-
slot,
|
|
101
|
-
client,
|
|
102
|
-
className,
|
|
103
|
-
buttonClassName,
|
|
104
|
-
size,
|
|
105
|
-
fallback = null
|
|
106
|
-
}) {
|
|
126
|
+
async function EditableCta({ slot, client, fallback = null, ...buttonProps }) {
|
|
107
127
|
const { data: ctas } = await client.ctas.list(slot).catch(() => ({ data: [] }));
|
|
108
128
|
if (ctas.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: fallback });
|
|
109
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CtaButtons, { ctas,
|
|
129
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CtaButtons, { ctas, ...buttonProps });
|
|
110
130
|
}
|
|
111
131
|
// Annotate the CommonJS export names for ESM import in node:
|
|
112
132
|
0 && (module.exports = {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/next-cache-adapter.ts","../src/cta-buttons.tsx","../src/sticky-mobile-cta.tsx","../src/editable-cta.tsx"],"sourcesContent":["/**\n * `@reblu/site-next` — Next.js RSC glue for Reblu SITEs (REB-418 / design doc §5).\n *\n * Two things live here, both framework-coupled to Next so the lower SDK layers\n * (`@reblu/site-client`, `@reblu/site-contracts`, `@reblu/site-ui`) stay\n * framework-agnostic:\n *\n * 1. {@link NextCacheAdapter} — implements `@reblu/site-client`'s\n * `RebluCacheAdapter`, translating cache policy into Next ISR `fetch` options.\n * 2. The editable-CTA glue (`EditableCta` server component + its `CtaButtons`\n * client boundary + `StickyMobileCta`), consuming the injected SDK client.\n *\n * `EditableCta` re-exports the `'use client'` `CtaButtons` from its own emitted\n * chunk, so the RSC boundary survives the build (see the R4 smoke test).\n */\nexport { NextCacheAdapter } from './next-cache-adapter'\nexport { CtaButtons, type CtaButtonsProps } from './cta-buttons'\nexport { StickyMobileCta, type StickyMobileCtaProps } from './sticky-mobile-cta'\nexport { EditableCta, type EditableCtaProps, type CtaClient, type CtaSlot } from './editable-cta'\n","import type { RebluCacheAdapter, RebluCachePolicy } from '@reblu/site-client'\n\n/**\n * Next.js ISR cache adapter (REB-418 / design doc §5.2).\n *\n * Implements the framework-agnostic {@link RebluCacheAdapter} interface exported\n * by `@reblu/site-client`, translating a {@link RebluCachePolicy} into the\n * `fetch` options Next understands. This is the piece that restores the exact\n * ISR behavior demo-site hard-codes today — now injected instead of baked in:\n *\n * ```ts\n * createRebluClient({ baseUrl, apiKey, cache: new NextCacheAdapter(60) })\n * ```\n *\n * - `revalidate` > 0 → `{ next: { revalidate } }` (time-based ISR)\n * - `revalidate` `false` / `0` → `{ cache: 'no-store' }` (dynamic / preview)\n * - omitted `revalidate` → the constructor `defaultRevalidate`\n */\nexport class NextCacheAdapter implements RebluCacheAdapter {\n /**\n * @param defaultRevalidate ISR window (seconds) applied when a request omits\n * its own `revalidate`. Defaults to `60` for parity with demo-site's current\n * ISR default (`client.ts:45`). Pass `false`/`0` to default to no-store.\n */\n constructor(private readonly defaultRevalidate: number | false = 60) {}\n\n resolveRequestInit(policy: RebluCachePolicy): RequestInit {\n const revalidate = policy.revalidate ?? this.defaultRevalidate\n if (revalidate === false || revalidate === 0) {\n return { cache: 'no-store' }\n }\n return { next: { revalidate } } as RequestInit\n }\n}\n","'use client'\n\nimport type { Cta } from '@reblu/site-contracts'\nimport { Button } from '@reblu/site-ui'\nimport Link from 'next/link'\n\ntype ButtonSize = 'sm' | 'default' | 'lg'\n\nexport interface CtaButtonsProps {\n ctas: Cta[]\n /** Wrapper class (layout of the button group). */\n className?: string\n /** Class applied to each button. */\n buttonClassName?: string\n size?: ButtonSize\n}\n\n/** External = absolute http(s) URL (e.g. a Klinikare booking link). */\nfunction isExternal(href: string): boolean {\n return /^https?:\\/\\//i.test(href)\n}\n\n/**\n * Presentational CTA renderer (REB-394, moved to `@reblu/site-next` in REB-418).\n * Pure — takes already-resolved CTAs and renders one styled button-link each.\n * Internal hrefs use `next/link` (client routing); external URLs open in a new,\n * safely-rel tab. `analyticsId` is surfaced as `data-cta-id` on the clickable\n * element for GA `cta_click` tracking. Renders nothing when there are no CTAs.\n *\n * Imports `Button` from `@reblu/site-ui` (public) — never `@reblu/ui` (private).\n */\nexport function CtaButtons({ ctas, className, buttonClassName, size = 'lg' }: CtaButtonsProps) {\n if (ctas.length === 0) return null\n\n return (\n <div className={className} data-slot='cta-buttons'>\n {ctas.map((cta) => (\n <Button\n key={cta.id}\n asChild\n size={size}\n className={buttonClassName}\n data-cta-id={cta.analyticsId ?? undefined}\n >\n {isExternal(cta.href) ? (\n <a href={cta.href} target='_blank' rel='noopener noreferrer'>\n {cta.label}\n </a>\n ) : (\n <Link href={cta.href}>{cta.label}</Link>\n )}\n </Button>\n ))}\n </div>\n )\n}\n","import type { Cta } from '@reblu/site-contracts'\n\nimport { CtaButtons } from './cta-buttons'\n\nexport interface StickyMobileCtaProps {\n ctas: Cta[]\n}\n\n/**\n * StickyMobileCta (REB-394, moved to `@reblu/site-next` in REB-418) — a\n * mobile-only sticky bottom bar surfacing the `sticky-mobile` slot CTAs. Hidden\n * on `md+` (desktop has its own CTAs). Renders nothing when no CTA is configured,\n * so it never adds empty chrome.\n */\nexport function StickyMobileCta({ ctas }: StickyMobileCtaProps) {\n if (ctas.length === 0) return null\n\n return (\n <div className='fixed inset-x-0 bottom-0 z-50 md:hidden border-t border-border bg-background/95 px-4 py-3 backdrop-blur supports-[backdrop-filter]:bg-background/80'>\n <CtaButtons\n ctas={ctas}\n className='flex justify-center gap-3'\n buttonClassName='w-full rounded-lg font-semibold'\n size='lg'\n />\n </div>\n )\n}\n","import type { RebluClient } from '@reblu/site-client'\nimport type { Cta } from '@reblu/site-contracts'\nimport type { ReactNode } from 'react'\n\nimport { CtaButtons } from './cta-buttons'\n\ntype ButtonSize = 'sm' | 'default' | 'lg'\n\n/** The `Cta.slot` union — the valid placement keys, sourced from the contract. */\nexport type CtaSlot = Cta['slot']\n\n/**\n * The slice of the SDK client `EditableCta` needs: just the `ctas` namespace.\n * Typing it structurally lets a SITE pass either the full `RebluClient` or a\n * narrower singleton, without coupling this component to the whole surface.\n */\nexport type CtaClient = Pick<RebluClient, 'ctas'>\n\nexport interface EditableCtaProps {\n /** CtaSlot value: hero | blog-end | services | contact | sticky-mobile | footer. */\n slot: CtaSlot\n /** The Reblu SDK client instance the SITE owns (env-configured upstream). */\n client: CtaClient\n className?: string\n buttonClassName?: string\n size?: ButtonSize\n /**\n * Rendered when the slot has no configured CTA (or the upstream fetch fails).\n * Lets each placement keep its existing static CTA as a graceful fallback.\n */\n fallback?: ReactNode\n}\n\n/**\n * EditableCta (REB-394, moved to `@reblu/site-next` in REB-418) — drop-anywhere\n * async Server Component that renders the CTAs configured for a slot from nexus,\n * with no redeploy. Fetches active CTAs via the injected SDK client and degrades\n * gracefully: on empty/failure it renders `fallback` (a static CTA) instead of\n * breaking the page.\n *\n * The client is injected (prop or per-SITE singleton) so this package never\n * reads `process.env` and stays framework-glue only — the SITE owns the env and\n * wires the ISR-aware {@link NextCacheAdapter}.\n */\nexport async function EditableCta({\n slot,\n client,\n className,\n buttonClassName,\n size,\n fallback = null,\n}: EditableCtaProps) {\n const { data: ctas } = await client.ctas\n .list(slot)\n .catch(() => ({ data: [] as Cta[] }))\n\n if (ctas.length === 0) return <>{fallback}</>\n\n return (\n <CtaButtons ctas={ctas} className={className} buttonClassName={buttonClassName} size={size} />\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACkBO,IAAM,mBAAN,MAAoD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMzD,YAA6B,oBAAoC,IAAI;AAAxC;AAAA,EAAyC;AAAA,EAAzC;AAAA,EAE7B,mBAAmB,QAAuC;AACxD,UAAM,aAAa,OAAO,cAAc,KAAK;AAC7C,QAAI,eAAe,SAAS,eAAe,GAAG;AAC5C,aAAO,EAAE,OAAO,WAAW;AAAA,IAC7B;AACA,WAAO,EAAE,MAAM,EAAE,WAAW,EAAE;AAAA,EAChC;AACF;;;AC9BA,qBAAuB;AACvB,kBAAiB;AAyCL;AA3BZ,SAAS,WAAW,MAAuB;AACzC,SAAO,gBAAgB,KAAK,IAAI;AAClC;AAWO,SAAS,WAAW,EAAE,MAAM,WAAW,iBAAiB,OAAO,KAAK,GAAoB;AAC7F,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,SACE,4CAAC,SAAI,WAAsB,aAAU,eAClC,eAAK,IAAI,CAAC,QACT;AAAA,IAAC;AAAA;AAAA,MAEC,SAAO;AAAA,MACP;AAAA,MACA,WAAW;AAAA,MACX,eAAa,IAAI,eAAe;AAAA,MAE/B,qBAAW,IAAI,IAAI,IAClB,4CAAC,OAAE,MAAM,IAAI,MAAM,QAAO,UAAS,KAAI,uBACpC,cAAI,OACP,IAEA,4CAAC,YAAAA,SAAA,EAAK,MAAM,IAAI,MAAO,cAAI,OAAM;AAAA;AAAA,IAX9B,IAAI;AAAA,EAaX,CACD,GACH;AAEJ;;;ACpCM,IAAAC,sBAAA;AALC,SAAS,gBAAgB,EAAE,KAAK,GAAyB;AAC9D,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,SACE,6CAAC,SAAI,WAAU,uJACb;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MACV,iBAAgB;AAAA,MAChB,MAAK;AAAA;AAAA,EACP,GACF;AAEJ;;;AC6BgC,IAAAC,sBAAA;AAZhC,eAAsB,YAAY;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AACb,GAAqB;AACnB,QAAM,EAAE,MAAM,KAAK,IAAI,MAAM,OAAO,KACjC,KAAK,IAAI,EACT,MAAM,OAAO,EAAE,MAAM,CAAC,EAAW,EAAE;AAEtC,MAAI,KAAK,WAAW,EAAG,QAAO,6EAAG,oBAAS;AAE1C,SACE,6CAAC,cAAW,MAAY,WAAsB,iBAAkC,MAAY;AAEhG;","names":["Link","import_jsx_runtime","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/next-cache-adapter.ts","../src/cta-buttons.tsx","../src/sticky-mobile-cta.tsx","../src/editable-cta.tsx"],"sourcesContent":["/**\n * `@reblu/site-next` — Next.js RSC glue for Reblu SITEs (REB-418 / design doc §5).\n *\n * Two things live here, both framework-coupled to Next so the lower SDK layers\n * (`@reblu/site-client`, `@reblu/site-contracts`, `@reblu/site-ui`) stay\n * framework-agnostic:\n *\n * 1. {@link NextCacheAdapter} — implements `@reblu/site-client`'s\n * `RebluCacheAdapter`, translating cache policy into Next ISR `fetch` options.\n * 2. The editable-CTA glue (`EditableCta` server component + its `CtaButtons`\n * client boundary + `StickyMobileCta`), consuming the injected SDK client.\n *\n * `EditableCta` re-exports the `'use client'` `CtaButtons` from its own emitted\n * chunk, so the RSC boundary survives the build (see the R4 smoke test).\n */\nexport { NextCacheAdapter } from './next-cache-adapter'\nexport { CtaButtons, type CtaButtonsProps, type CtaButtonVariant } from './cta-buttons'\nexport { StickyMobileCta, type StickyMobileCtaProps } from './sticky-mobile-cta'\nexport { EditableCta, type EditableCtaProps, type CtaClient, type CtaSlot } from './editable-cta'\n","import type { RebluCacheAdapter, RebluCachePolicy } from '@reblu/site-client'\n\n/**\n * Next.js ISR cache adapter (REB-418 / design doc §5.2).\n *\n * Implements the framework-agnostic {@link RebluCacheAdapter} interface exported\n * by `@reblu/site-client`, translating a {@link RebluCachePolicy} into the\n * `fetch` options Next understands. This is the piece that restores the exact\n * ISR behavior demo-site hard-codes today — now injected instead of baked in:\n *\n * ```ts\n * createRebluClient({ baseUrl, apiKey, cache: new NextCacheAdapter(60) })\n * ```\n *\n * - `revalidate` > 0 → `{ next: { revalidate } }` (time-based ISR)\n * - `revalidate` `false` / `0` → `{ cache: 'no-store' }` (dynamic / preview)\n * - omitted `revalidate` → the constructor `defaultRevalidate`\n */\nexport class NextCacheAdapter implements RebluCacheAdapter {\n /**\n * @param defaultRevalidate ISR window (seconds) applied when a request omits\n * its own `revalidate`. Defaults to `60` for parity with demo-site's current\n * ISR default (`client.ts:45`). Pass `false`/`0` to default to no-store.\n */\n constructor(private readonly defaultRevalidate: number | false = 60) {}\n\n resolveRequestInit(policy: RebluCachePolicy): RequestInit {\n const revalidate = policy.revalidate ?? this.defaultRevalidate\n if (revalidate === false || revalidate === 0) {\n return { cache: 'no-store' }\n }\n return { next: { revalidate } } as RequestInit\n }\n}\n","'use client'\n\nimport type { Cta } from '@reblu/site-contracts'\nimport { Button, cn, type ButtonVariantProps } from '@reblu/site-ui'\nimport Link from 'next/link'\nimport type { ReactNode } from 'react'\n\ntype ButtonSize = 'sm' | 'default' | 'lg'\n\n/** A `@reblu/site-ui` Button variant (`default`, `outline`, `secondary`, …). */\nexport type CtaButtonVariant = NonNullable<ButtonVariantProps['variant']>\n\n/** Default screen-reader hint appended to CTAs that open in a new tab. */\nconst DEFAULT_NEW_WINDOW_HINT = '(se abre en una nueva ventana)'\n\nexport interface CtaButtonsProps {\n ctas: Cta[]\n /** Wrapper class (layout of the button group). */\n className?: string\n /** Class applied to each button. */\n buttonClassName?: string\n size?: ButtonSize\n /**\n * Leading icon rendered before every CTA label. Decorative: it is wrapped in\n * an `aria-hidden` span, so the accessible name stays the label.\n */\n icon?: ReactNode\n /** Button variant of the primary CTA (the first one, by `sortOrder`). Default `default`. */\n primaryVariant?: CtaButtonVariant\n /** Button variant of every secondary CTA (all but the first). Default `default`. */\n secondaryVariant?: CtaButtonVariant\n /**\n * Extra class for the primary CTA, merged after `buttonClassName`. Lets a SITE\n * map the primary CTA to its own button style (e.g. a brand `cta` variant).\n */\n primaryClassName?: string\n /** Extra class for every secondary CTA, merged after `buttonClassName`. */\n secondaryClassName?: string\n /**\n * Visually-hidden text appended to CTAs that open in a new tab, so screen\n * readers announce the context change. Default «(se abre en una nueva ventana)».\n */\n newWindowHint?: string\n}\n\n/** External = absolute http(s) URL (e.g. a third-party booking link). */\nfunction isExternal(href: string): boolean {\n return /^https?:\\/\\//i.test(href)\n}\n\n/**\n * Presentational CTA renderer (REB-394, moved to `@reblu/site-next` in REB-418).\n * Pure — takes already-resolved CTAs and renders one styled button-link each.\n * Internal hrefs use `next/link` (client routing); external URLs open in a new,\n * safely-rel tab and carry a visually-hidden new-window hint. `analyticsId` is\n * surfaced as `data-cta-id` on the clickable element for GA `cta_click`\n * tracking. Renders nothing when there are no CTAs.\n *\n * The first CTA is the primary one; the rest are secondary. Each role takes its\n * own Button variant and extra class, so a SITE maps the CTAs to its own button\n * styles without forking this markup.\n *\n * Imports `Button` from `@reblu/site-ui` (public) — never `@reblu/ui` (private).\n */\nexport function CtaButtons({\n ctas,\n className,\n buttonClassName,\n size = 'lg',\n icon,\n primaryVariant = 'default',\n secondaryVariant = 'default',\n primaryClassName,\n secondaryClassName,\n newWindowHint = DEFAULT_NEW_WINDOW_HINT,\n}: CtaButtonsProps) {\n if (ctas.length === 0) return null\n\n const leadingIcon =\n icon == null ? null : (\n <span aria-hidden='true' className='inline-flex shrink-0'>\n {icon}\n </span>\n )\n\n return (\n <div className={className} data-slot='cta-buttons'>\n {ctas.map((cta, index) => {\n const isPrimary = index === 0\n return (\n <Button\n key={cta.id}\n asChild\n size={size}\n variant={isPrimary ? primaryVariant : secondaryVariant}\n className={cn(buttonClassName, isPrimary ? primaryClassName : secondaryClassName)}\n data-cta-id={cta.analyticsId ?? undefined}\n >\n {isExternal(cta.href) ? (\n <a href={cta.href} target='_blank' rel='noopener noreferrer'>\n {leadingIcon}\n {cta.label}\n {newWindowHint ? <> <span className='sr-only'>{newWindowHint}</span></> : null}\n </a>\n ) : (\n <Link href={cta.href}>\n {leadingIcon}\n {cta.label}\n </Link>\n )}\n </Button>\n )\n })}\n </div>\n )\n}\n","import type { Cta } from '@reblu/site-contracts'\n\nimport { CtaButtons } from './cta-buttons'\n\nexport interface StickyMobileCtaProps {\n ctas: Cta[]\n}\n\n/**\n * StickyMobileCta (REB-394, moved to `@reblu/site-next` in REB-418) — a\n * mobile-only sticky bottom bar surfacing the `sticky-mobile` slot CTAs. Hidden\n * on `md+` (desktop has its own CTAs). Renders nothing when no CTA is configured,\n * so it never adds empty chrome.\n */\nexport function StickyMobileCta({ ctas }: StickyMobileCtaProps) {\n if (ctas.length === 0) return null\n\n return (\n <div className='fixed inset-x-0 bottom-0 z-50 md:hidden border-t border-border bg-background/95 px-4 py-3 backdrop-blur supports-[backdrop-filter]:bg-background/80'>\n <CtaButtons\n ctas={ctas}\n className='flex justify-center gap-3'\n buttonClassName='w-full rounded-lg font-semibold'\n size='lg'\n />\n </div>\n )\n}\n","import type { RebluClient } from '@reblu/site-client'\nimport type { Cta } from '@reblu/site-contracts'\nimport type { ReactNode } from 'react'\n\nimport { CtaButtons, type CtaButtonsProps } from './cta-buttons'\n\n/** The `Cta.slot` union — the valid placement keys, sourced from the contract. */\nexport type CtaSlot = Cta['slot']\n\n/**\n * The slice of the SDK client `EditableCta` needs: just the `ctas` namespace.\n * Typing it structurally lets a SITE pass either the full `RebluClient` or a\n * narrower singleton, without coupling this component to the whole surface.\n */\nexport type CtaClient = Pick<RebluClient, 'ctas'>\n\n/** Presentation props `EditableCta` forwards unchanged to {@link CtaButtons}. */\ntype ForwardedButtonProps = Omit<CtaButtonsProps, 'ctas'>\n\nexport interface EditableCtaProps extends ForwardedButtonProps {\n /** CtaSlot value: header | hero | blog-end | services | contact | sticky-mobile | footer. */\n slot: CtaSlot\n /** The Reblu SDK client instance the SITE owns (env-configured upstream). */\n client: CtaClient\n /**\n * Rendered when the slot has no configured CTA (or the upstream fetch fails).\n * Lets each placement keep its existing static CTA as a graceful fallback.\n */\n fallback?: ReactNode\n}\n\n/**\n * EditableCta (REB-394, moved to `@reblu/site-next` in REB-418) — drop-anywhere\n * async Server Component that renders the CTAs configured for a slot from nexus,\n * with no redeploy. Fetches active CTAs via the injected SDK client and degrades\n * gracefully: on empty/failure it renders `fallback` (a static CTA) instead of\n * breaking the page.\n *\n * The client is injected (prop or per-SITE singleton) so this package never\n * reads `process.env` and stays framework-glue only — the SITE owns the env and\n * wires the ISR-aware {@link NextCacheAdapter}.\n */\nexport async function EditableCta({ slot, client, fallback = null, ...buttonProps }: EditableCtaProps) {\n const { data: ctas } = await client.ctas\n .list(slot)\n .catch(() => ({ data: [] as Cta[] }))\n\n if (ctas.length === 0) return <>{fallback}</>\n\n return <CtaButtons ctas={ctas} {...buttonProps} />\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACkBO,IAAM,mBAAN,MAAoD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMzD,YAA6B,oBAAoC,IAAI;AAAxC;AAAA,EAAyC;AAAA,EAAzC;AAAA,EAE7B,mBAAmB,QAAuC;AACxD,UAAM,aAAa,OAAO,cAAc,KAAK;AAC7C,QAAI,eAAe,SAAS,eAAe,GAAG;AAC5C,aAAO,EAAE,OAAO,WAAW;AAAA,IAC7B;AACA,WAAO,EAAE,MAAM,EAAE,WAAW,EAAE;AAAA,EAChC;AACF;;;AC9BA,qBAAoD;AACpD,kBAAiB;AA4EX;AAnEN,IAAM,0BAA0B;AAiChC,SAAS,WAAW,MAAuB;AACzC,SAAO,gBAAgB,KAAK,IAAI;AAClC;AAgBO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB;AAAA,EACA;AAAA,EACA,gBAAgB;AAClB,GAAoB;AAClB,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QAAM,cACJ,QAAQ,OAAO,OACb,4CAAC,UAAK,eAAY,QAAO,WAAU,wBAChC,gBACH;AAGJ,SACE,4CAAC,SAAI,WAAsB,aAAU,eAClC,eAAK,IAAI,CAAC,KAAK,UAAU;AACxB,UAAM,YAAY,UAAU;AAC5B,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,SAAO;AAAA,QACP;AAAA,QACA,SAAS,YAAY,iBAAiB;AAAA,QACtC,eAAW,mBAAG,iBAAiB,YAAY,mBAAmB,kBAAkB;AAAA,QAChF,eAAa,IAAI,eAAe;AAAA,QAE/B,qBAAW,IAAI,IAAI,IAClB,6CAAC,OAAE,MAAM,IAAI,MAAM,QAAO,UAAS,KAAI,uBACpC;AAAA;AAAA,UACA,IAAI;AAAA,UACJ,gBAAgB,4EAAE;AAAA;AAAA,YAAC,4CAAC,UAAK,WAAU,WAAW,yBAAc;AAAA,aAAO,IAAM;AAAA,WAC5E,IAEA,6CAAC,YAAAA,SAAA,EAAK,MAAM,IAAI,MACb;AAAA;AAAA,UACA,IAAI;AAAA,WACP;AAAA;AAAA,MAjBG,IAAI;AAAA,IAmBX;AAAA,EAEJ,CAAC,GACH;AAEJ;;;AChGM,IAAAC,sBAAA;AALC,SAAS,gBAAgB,EAAE,KAAK,GAAyB;AAC9D,MAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,SACE,6CAAC,SAAI,WAAU,uJACb;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MACV,iBAAgB;AAAA,MAChB,MAAK;AAAA;AAAA,EACP,GACF;AAEJ;;;ACoBgC,IAAAC,sBAAA;AALhC,eAAsB,YAAY,EAAE,MAAM,QAAQ,WAAW,MAAM,GAAG,YAAY,GAAqB;AACrG,QAAM,EAAE,MAAM,KAAK,IAAI,MAAM,OAAO,KACjC,KAAK,IAAI,EACT,MAAM,OAAO,EAAE,MAAM,CAAC,EAAW,EAAE;AAEtC,MAAI,KAAK,WAAW,EAAG,QAAO,6EAAG,oBAAS;AAE1C,SAAO,6CAAC,cAAW,MAAa,GAAG,aAAa;AAClD;","names":["Link","import_jsx_runtime","import_jsx_runtime"]}
|