@reblu/site-next 1.19.0 → 1.20.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 +30 -0
- package/README.md +70 -9
- package/dist/analytics.js +1 -1
- package/dist/auth.js +3 -3
- package/dist/consent.js +1 -1
- package/dist/cta-buttons.cjs +51 -12
- package/dist/cta-buttons.cjs.map +1 -1
- package/dist/cta-buttons.d.cts +38 -4
- package/dist/cta-buttons.d.ts +38 -4
- package/dist/cta-buttons.js +54 -15
- package/dist/cta-buttons.js.map +1 -1
- package/dist/index.cjs +53 -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 +58 -26
- package/dist/index.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/dist/shop.js +2 -2
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @reblu/site-next
|
|
2
2
|
|
|
3
|
+
## 1.20.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 146c45a: ESM build imports `next/link.js`, `next/navigation.js` and `next/script.js`, so plain Node ESM (a SITE's vitest) resolves them; the `server.deps.inline` workaround can be removed (REB-929). `CtaButtons` renders an element `icon` as a direct, `aria-hidden` child of the button again, so shadcn's `has-[>svg]` icon padding applies (REB-930).
|
|
8
|
+
- Updated dependencies [146c45a]
|
|
9
|
+
- @reblu/site-ui@0.7.1
|
|
10
|
+
|
|
11
|
+
## 1.20.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 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`).
|
|
16
|
+
- `icon` — leading icon on every CTA, wrapped in an `aria-hidden` span.
|
|
17
|
+
- `primaryVariant` / `secondaryVariant` — `@reblu/site-ui` Button variant for the first CTA and for the rest (default `'default'`).
|
|
18
|
+
- `primaryClassName` / `secondaryClassName` — extra class per role, merged after `buttonClassName`, to map CTAs to the SITE's own button styles.
|
|
19
|
+
- `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.
|
|
20
|
+
|
|
21
|
+
`EditableCta` forwards every `CtaButtons` presentation prop and accepts the new `header` slot. New exported type: `CtaButtonVariant`.
|
|
22
|
+
|
|
23
|
+
- 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.
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [92e0f6f]
|
|
28
|
+
- Updated dependencies [b75f066]
|
|
29
|
+
- Updated dependencies [b75f066]
|
|
30
|
+
- @reblu/site-contracts@1.14.0
|
|
31
|
+
- @reblu/site-client@5.6.0
|
|
32
|
+
|
|
3
33
|
## 1.19.0
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -45,6 +45,15 @@ to preserve the `'use client'` directive — R4), so importing anything from
|
|
|
45
45
|
`@reblu/site-next/auth` pulls in `AuthLogo`'s `next-themes` import at module
|
|
46
46
|
resolution time, even if your code never renders `<AuthLogo/>`.
|
|
47
47
|
|
|
48
|
+
### Testing a SITE with plain Node ESM (vitest)
|
|
49
|
+
|
|
50
|
+
Since 1.20.1 the ESM build imports `next/link.js`, `next/navigation.js` and
|
|
51
|
+
`next/script.js` (Next ships no `exports` map, so plain Node ESM needs the
|
|
52
|
+
extension). A SITE's vitest config no longer needs
|
|
53
|
+
`test.server.deps.inline: [/@reblu\/site-next/]` to render SDK components; remove
|
|
54
|
+
that workaround when you upgrade. The same fix ships in `@reblu/site-ui` for
|
|
55
|
+
`./next` and `./pagination`.
|
|
56
|
+
|
|
48
57
|
## ISR caching
|
|
49
58
|
|
|
50
59
|
Wire the adapter once when you build the SDK client — this restores the exact ISR
|
|
@@ -80,6 +89,43 @@ import { client } from '@/lib/reblu'
|
|
|
80
89
|
<EditableCta slot="hero" client={client} fallback={<StaticHeroCta />} />
|
|
81
90
|
```
|
|
82
91
|
|
|
92
|
+
Slots: `header`, `hero`, `blog-end`, `services`, `contact`, `sticky-mobile`,
|
|
93
|
+
`footer`. A persistent header CTA, for example:
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { CalendarIcon } from 'lucide-react'
|
|
97
|
+
|
|
98
|
+
<EditableCta
|
|
99
|
+
slot="header"
|
|
100
|
+
client={client}
|
|
101
|
+
size="sm"
|
|
102
|
+
icon={<CalendarIcon />}
|
|
103
|
+
primaryVariant="default"
|
|
104
|
+
secondaryVariant="outline"
|
|
105
|
+
primaryClassName="btn-cta" // optional: map the primary CTA to your own button style
|
|
106
|
+
fallback={<StaticHeaderCta />}
|
|
107
|
+
/>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`EditableCta` forwards every `CtaButtons` presentation prop:
|
|
111
|
+
|
|
112
|
+
| Prop | Default | Effect |
|
|
113
|
+
| -------------------- | ---------------------------------- | ------------------------------------------------------- |
|
|
114
|
+
| `className` | — | Wrapper class (layout of the button group) |
|
|
115
|
+
| `buttonClassName` | — | Class on every button |
|
|
116
|
+
| `size` | `'lg'` | `@reblu/site-ui` Button size |
|
|
117
|
+
| `icon` | — | Leading icon on every CTA, `aria-hidden`; an element icon is a direct child, so `has-[>svg]` padding applies |
|
|
118
|
+
| `primaryVariant` | `'default'` | `@reblu/site-ui` Button variant of the first CTA |
|
|
119
|
+
| `secondaryVariant` | `'default'` | Button variant of every other CTA |
|
|
120
|
+
| `primaryClassName` | — | Extra class on the first CTA, after `buttonClassName` |
|
|
121
|
+
| `secondaryClassName` | — | Extra class on every other CTA, after `buttonClassName` |
|
|
122
|
+
| `newWindowHint` | `'(se abre en una nueva ventana)'` | Visually-hidden text on CTAs that open in a new tab |
|
|
123
|
+
|
|
124
|
+
CTAs with an absolute `http(s)` href open in a new tab
|
|
125
|
+
(`target="_blank" rel="noopener noreferrer"`) and carry the `newWindowHint` in a
|
|
126
|
+
`sr-only` span, so screen readers announce the new window. The primary CTA is the
|
|
127
|
+
first one by `sortOrder`.
|
|
128
|
+
|
|
83
129
|
`CtaButtons` (the `'use client'` renderer) and `StickyMobileCta` are also exported.
|
|
84
130
|
`CtaButtons` is additionally available at the `@reblu/site-next/cta-buttons`
|
|
85
131
|
subpath, which resolves directly to the emitted `'use client'` boundary.
|
|
@@ -325,7 +371,7 @@ its own Client Component.
|
|
|
325
371
|
| `createShopCheckoutRoute(opts?)` | `./shop-route-handlers` | No |
|
|
326
372
|
| `createShopValidateCartRoute()` | `./shop-route-handlers` | No |
|
|
327
373
|
| `createShopApplyDiscountRoute()` | `./shop-route-handlers` | No |
|
|
328
|
-
| `createShopCheckEmailRoute()` | `./shop-route-handlers` | No |
|
|
374
|
+
| `createShopCheckEmailRoute()` — **deprecated** (REB-834) | `./shop-route-handlers` | No |
|
|
329
375
|
| `createShopOrderRoute()` | `./shop-route-handlers` | No |
|
|
330
376
|
| `getShopOrderSummary(client, sessionId)` | `./shop-server` | No |
|
|
331
377
|
| `useShopOrderStatus(sessionId, opts?)` | `./shop` | **Yes** |
|
|
@@ -344,7 +390,7 @@ its own Client Component.
|
|
|
344
390
|
<CartProvider>{children}</CartProvider>
|
|
345
391
|
```
|
|
346
392
|
|
|
347
|
-
2. **Route Handlers** — mount the
|
|
393
|
+
2. **Route Handlers** — mount the four factories, one file each (mirrors the
|
|
348
394
|
`./route-handlers` auth pattern — a one-line file per route):
|
|
349
395
|
|
|
350
396
|
```ts
|
|
@@ -358,22 +404,37 @@ its own Client Component.
|
|
|
358
404
|
// app/api/shop/apply-discount/route.ts
|
|
359
405
|
export const POST = createShopApplyDiscountRoute()
|
|
360
406
|
|
|
361
|
-
// app/api/shop/check-email/route.ts
|
|
362
|
-
export const POST = createShopCheckEmailRoute()
|
|
363
|
-
|
|
364
407
|
// app/api/shop/order/[sessionId]/route.ts
|
|
365
408
|
export const GET = createShopOrderRoute()
|
|
366
409
|
```
|
|
367
410
|
|
|
368
|
-
All
|
|
369
|
-
(same as `./route-handlers`'s `createProxyRoute`, which they
|
|
370
|
-
API key never reaches the browser.
|
|
411
|
+
All of them read `REBLU_API_BASE_URL`/`REBLU_API_KEY` lazily from
|
|
412
|
+
`process.env` (same as `./route-handlers`'s `createProxyRoute`, which they
|
|
413
|
+
wrap) — the API key never reaches the browser.
|
|
414
|
+
|
|
415
|
+
Do not mount `createShopCheckEmailRoute()` on a new SITE: it is
|
|
416
|
+
**deprecated** (REB-834) and removed in the next major. Its endpoint used to
|
|
417
|
+
say whether an email had an account — an enumeration oracle on the
|
|
418
|
+
tenant's customer list — and now always answers `{ exists: false }`. An
|
|
419
|
+
existing mount keeps working; remove it at your convenience. The checkout
|
|
420
|
+
always offers «Iniciar sesión o continuar como invitado», and the server
|
|
421
|
+
links a guest's order to an existing account after payment.
|
|
422
|
+
|
|
423
|
+
**Forward the visitor's IP.** base-api rate-limits some public routes
|
|
424
|
+
per client IP (check-email, and the auth routes such as login and
|
|
425
|
+
magic-link), reading the IP from `x-forwarded-for`. A SITE
|
|
426
|
+
that calls `/api/site/*` from its own server must forward the visitor's
|
|
427
|
+
`x-forwarded-for` (and `x-real-ip`), or every visitor shares the SITE
|
|
428
|
+
server's single IP window and one busy page throttles them all.
|
|
429
|
+
`createProxyRoute` — and therefore every `createShop*Route` factory —
|
|
430
|
+
already forwards both headers; only hand-written server-side calls need to
|
|
431
|
+
do it themselves.
|
|
371
432
|
|
|
372
433
|
3. **Pages** — create `app/shop/cart/page.tsx`, `app/shop/checkout/page.tsx`
|
|
373
434
|
and `app/shop/order-confirmation/page.tsx`. Read the cart with `useCart()`
|
|
374
435
|
from `./cart`, render `@reblu/site-blocks`'s `CartLineList`/`CartSummary`/
|
|
375
436
|
`CheckoutForm`/`OrderConfirmation`, and `fetch` the Route Handlers from
|
|
376
|
-
step 2 for checkout/validate-cart/apply-discount/
|
|
437
|
+
step 2 for checkout/validate-cart/apply-discount/order (never
|
|
377
438
|
call base-api directly from a Client Component — that's what step 2's
|
|
378
439
|
Route Handlers are for). See `apps/demo-site`'s `shop/{cart,checkout,
|
|
379
440
|
order-confirmation}/page.tsx` in this monorepo for a complete, working
|
package/dist/analytics.js
CHANGED
package/dist/auth.js
CHANGED
|
@@ -726,7 +726,7 @@ function ResetPasswordRequest(props) {
|
|
|
726
726
|
|
|
727
727
|
// src/auth/reset-password-confirm.tsx
|
|
728
728
|
import * as React5 from "react";
|
|
729
|
-
import { useRouter, useSearchParams } from "next/navigation";
|
|
729
|
+
import { useRouter, useSearchParams } from "next/navigation.js";
|
|
730
730
|
import { Button as Button4, Input as Input3, cn as cn5 } from "@reblu/site-ui";
|
|
731
731
|
import { Label as Label3 } from "@reblu/site-ui/label";
|
|
732
732
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
@@ -909,7 +909,7 @@ function ResetPasswordConfirm(props) {
|
|
|
909
909
|
|
|
910
910
|
// src/auth/set-password.tsx
|
|
911
911
|
import * as React6 from "react";
|
|
912
|
-
import { useSearchParams as useSearchParams2 } from "next/navigation";
|
|
912
|
+
import { useSearchParams as useSearchParams2 } from "next/navigation.js";
|
|
913
913
|
import { Button as Button5, Input as Input4, cn as cn6 } from "@reblu/site-ui";
|
|
914
914
|
import { Label as Label4 } from "@reblu/site-ui/label";
|
|
915
915
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
@@ -1042,7 +1042,7 @@ function SetPassword(props) {
|
|
|
1042
1042
|
|
|
1043
1043
|
// src/auth/auth-confirm.tsx
|
|
1044
1044
|
import * as React7 from "react";
|
|
1045
|
-
import { useSearchParams as useSearchParams3 } from "next/navigation";
|
|
1045
|
+
import { useSearchParams as useSearchParams3 } from "next/navigation.js";
|
|
1046
1046
|
import { cn as cn7 } from "@reblu/site-ui";
|
|
1047
1047
|
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1048
1048
|
var DEFAULT_COPY8 = {
|
package/dist/consent.js
CHANGED
package/dist/cta-buttons.cjs
CHANGED
|
@@ -36,23 +36,62 @@ __export(cta_buttons_exports, {
|
|
|
36
36
|
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
|
+
var import_react = require("react");
|
|
39
40
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
41
|
+
var DEFAULT_NEW_WINDOW_HINT = "(se abre en una nueva ventana)";
|
|
40
42
|
function isExternal(href) {
|
|
41
43
|
return /^https?:\/\//i.test(href);
|
|
42
44
|
}
|
|
43
|
-
function
|
|
45
|
+
function renderLeadingIcon(icon) {
|
|
46
|
+
if (icon == null || icon === false) return null;
|
|
47
|
+
if ((0, import_react.isValidElement)(icon) && icon.type !== import_react.Fragment) {
|
|
48
|
+
const element = icon;
|
|
49
|
+
return (0, import_react.cloneElement)(element, {
|
|
50
|
+
"aria-hidden": true,
|
|
51
|
+
className: (0, import_site_ui.cn)(element.props.className, "shrink-0")
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", className: "contents", children: icon });
|
|
55
|
+
}
|
|
56
|
+
function CtaButtons({
|
|
57
|
+
ctas,
|
|
58
|
+
className,
|
|
59
|
+
buttonClassName,
|
|
60
|
+
size = "lg",
|
|
61
|
+
icon,
|
|
62
|
+
primaryVariant = "default",
|
|
63
|
+
secondaryVariant = "default",
|
|
64
|
+
primaryClassName,
|
|
65
|
+
secondaryClassName,
|
|
66
|
+
newWindowHint = DEFAULT_NEW_WINDOW_HINT
|
|
67
|
+
}) {
|
|
44
68
|
if (ctas.length === 0) return null;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
const leadingIcon = renderLeadingIcon(icon);
|
|
70
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, "data-slot": "cta-buttons", children: ctas.map((cta, index) => {
|
|
71
|
+
const isPrimary = index === 0;
|
|
72
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
73
|
+
import_site_ui.Button,
|
|
74
|
+
{
|
|
75
|
+
asChild: true,
|
|
76
|
+
size,
|
|
77
|
+
variant: isPrimary ? primaryVariant : secondaryVariant,
|
|
78
|
+
className: (0, import_site_ui.cn)(buttonClassName, isPrimary ? primaryClassName : secondaryClassName),
|
|
79
|
+
"data-cta-id": cta.analyticsId ?? void 0,
|
|
80
|
+
children: isExternal(cta.href) ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: cta.href, target: "_blank", rel: "noopener noreferrer", children: [
|
|
81
|
+
leadingIcon,
|
|
82
|
+
cta.label,
|
|
83
|
+
newWindowHint ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
84
|
+
" ",
|
|
85
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sr-only", children: newWindowHint })
|
|
86
|
+
] }) : null
|
|
87
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href: cta.href, children: [
|
|
88
|
+
leadingIcon,
|
|
89
|
+
cta.label
|
|
90
|
+
] })
|
|
91
|
+
},
|
|
92
|
+
cta.id
|
|
93
|
+
);
|
|
94
|
+
}) });
|
|
56
95
|
}
|
|
57
96
|
// Annotate the CommonJS export names for ESM import in node:
|
|
58
97
|
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 { cloneElement, Fragment, isValidElement, type ReactElement, 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 marked\n * `aria-hidden`, so the accessible name stays the label. A custom icon\n * component must forward `aria-hidden` and `className` to its root element. An element icon (an\n * `<svg>` or an icon component) is rendered as a direct child of the button,\n * so shadcn's `has-[>svg]` icon padding still matches; anything else is\n * wrapped in an `aria-hidden` `display: contents` span.\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 * REB-930 — no wrapper around an element icon: `has-[>svg]:px-*` only matches an\n * svg that is a DIRECT child of the button. `display: contents` on the fallback\n * wrapper keeps it out of the flex layout, though a wrapped svg is still not a\n * direct child.\n */\nfunction renderLeadingIcon(icon: ReactNode): ReactNode {\n if (icon == null || icon === false) return null\n // A fragment cannot carry aria-hidden, so it takes the wrapper like a non-element.\n if (isValidElement(icon) && icon.type !== Fragment) {\n const element = icon as ReactElement<{ 'aria-hidden'?: boolean | 'true'; className?: string }>\n // shrink-0: the old wrapper kept any icon from shrinking in a narrow button;\n // shadcn's `[&_svg]:shrink-0` only covers svg.\n return cloneElement(element, {\n 'aria-hidden': true,\n className: cn(element.props.className, 'shrink-0'),\n })\n }\n return (\n <span aria-hidden='true' className='contents'>\n {icon}\n </span>\n )\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 = renderLeadingIcon(icon)\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 ? (\n <>\n {' '}\n <span className='sr-only'>{newWindowHint}</span>\n </>\n ) : 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;AACjB,mBAA0F;AAoEtF;AA5DJ,IAAM,0BAA0B;AAqChC,SAAS,WAAW,MAAuB;AACzC,SAAO,gBAAgB,KAAK,IAAI;AAClC;AAQA,SAAS,kBAAkB,MAA4B;AACrD,MAAI,QAAQ,QAAQ,SAAS,MAAO,QAAO;AAE3C,UAAI,6BAAe,IAAI,KAAK,KAAK,SAAS,uBAAU;AAClD,UAAM,UAAU;AAGhB,eAAO,2BAAa,SAAS;AAAA,MAC3B,eAAe;AAAA,MACf,eAAW,mBAAG,QAAQ,MAAM,WAAW,UAAU;AAAA,IACnD,CAAC;AAAA,EACH;AACA,SACE,4CAAC,UAAK,eAAY,QAAO,WAAU,YAChC,gBACH;AAEJ;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,cAAc,kBAAkB,IAAI;AAE1C,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,gBACC,4EACG;AAAA;AAAA,YACD,4CAAC,UAAK,WAAU,WAAW,yBAAc;AAAA,aAC3C,IACE;AAAA,WACN,IAEA,6CAAC,YAAAA,SAAA,EAAK,MAAM,IAAI,MACb;AAAA;AAAA,UACA,IAAI;AAAA,WACP;AAAA;AAAA,MAtBG,IAAI;AAAA,IAwBX;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,46 @@ 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 marked
|
|
18
|
+
* `aria-hidden`, so the accessible name stays the label. A custom icon
|
|
19
|
+
* component must forward `aria-hidden` and `className` to its root element. An element icon (an
|
|
20
|
+
* `<svg>` or an icon component) is rendered as a direct child of the button,
|
|
21
|
+
* so shadcn's `has-[>svg]` icon padding still matches; anything else is
|
|
22
|
+
* wrapped in an `aria-hidden` `display: contents` span.
|
|
23
|
+
*/
|
|
24
|
+
icon?: ReactNode;
|
|
25
|
+
/** Button variant of the primary CTA (the first one, by `sortOrder`). Default `default`. */
|
|
26
|
+
primaryVariant?: CtaButtonVariant;
|
|
27
|
+
/** Button variant of every secondary CTA (all but the first). Default `default`. */
|
|
28
|
+
secondaryVariant?: CtaButtonVariant;
|
|
29
|
+
/**
|
|
30
|
+
* Extra class for the primary CTA, merged after `buttonClassName`. Lets a SITE
|
|
31
|
+
* map the primary CTA to its own button style (e.g. a brand `cta` variant).
|
|
32
|
+
*/
|
|
33
|
+
primaryClassName?: string;
|
|
34
|
+
/** Extra class for every secondary CTA, merged after `buttonClassName`. */
|
|
35
|
+
secondaryClassName?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Visually-hidden text appended to CTAs that open in a new tab, so screen
|
|
38
|
+
* readers announce the context change. Default «(se abre en una nueva ventana)».
|
|
39
|
+
*/
|
|
40
|
+
newWindowHint?: string;
|
|
12
41
|
}
|
|
13
42
|
/**
|
|
14
43
|
* Presentational CTA renderer (REB-394, moved to `@reblu/site-next` in REB-418).
|
|
15
44
|
* Pure — takes already-resolved CTAs and renders one styled button-link each.
|
|
16
45
|
* Internal hrefs use `next/link` (client routing); external URLs open in a new,
|
|
17
|
-
* safely-rel tab
|
|
18
|
-
*
|
|
46
|
+
* safely-rel tab and carry a visually-hidden new-window hint. `analyticsId` is
|
|
47
|
+
* surfaced as `data-cta-id` on the clickable element for GA `cta_click`
|
|
48
|
+
* tracking. Renders nothing when there are no CTAs.
|
|
49
|
+
*
|
|
50
|
+
* The first CTA is the primary one; the rest are secondary. Each role takes its
|
|
51
|
+
* own Button variant and extra class, so a SITE maps the CTAs to its own button
|
|
52
|
+
* styles without forking this markup.
|
|
19
53
|
*
|
|
20
54
|
* Imports `Button` from `@reblu/site-ui` (public) — never `@reblu/ui` (private).
|
|
21
55
|
*/
|
|
22
|
-
declare function CtaButtons({ ctas, className, buttonClassName, size }: CtaButtonsProps): react_jsx_runtime.JSX.Element | null;
|
|
56
|
+
declare function CtaButtons({ ctas, className, buttonClassName, size, icon, primaryVariant, secondaryVariant, primaryClassName, secondaryClassName, newWindowHint, }: CtaButtonsProps): react_jsx_runtime.JSX.Element | null;
|
|
23
57
|
|
|
24
|
-
export { CtaButtons, type CtaButtonsProps };
|
|
58
|
+
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,46 @@ 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 marked
|
|
18
|
+
* `aria-hidden`, so the accessible name stays the label. A custom icon
|
|
19
|
+
* component must forward `aria-hidden` and `className` to its root element. An element icon (an
|
|
20
|
+
* `<svg>` or an icon component) is rendered as a direct child of the button,
|
|
21
|
+
* so shadcn's `has-[>svg]` icon padding still matches; anything else is
|
|
22
|
+
* wrapped in an `aria-hidden` `display: contents` span.
|
|
23
|
+
*/
|
|
24
|
+
icon?: ReactNode;
|
|
25
|
+
/** Button variant of the primary CTA (the first one, by `sortOrder`). Default `default`. */
|
|
26
|
+
primaryVariant?: CtaButtonVariant;
|
|
27
|
+
/** Button variant of every secondary CTA (all but the first). Default `default`. */
|
|
28
|
+
secondaryVariant?: CtaButtonVariant;
|
|
29
|
+
/**
|
|
30
|
+
* Extra class for the primary CTA, merged after `buttonClassName`. Lets a SITE
|
|
31
|
+
* map the primary CTA to its own button style (e.g. a brand `cta` variant).
|
|
32
|
+
*/
|
|
33
|
+
primaryClassName?: string;
|
|
34
|
+
/** Extra class for every secondary CTA, merged after `buttonClassName`. */
|
|
35
|
+
secondaryClassName?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Visually-hidden text appended to CTAs that open in a new tab, so screen
|
|
38
|
+
* readers announce the context change. Default «(se abre en una nueva ventana)».
|
|
39
|
+
*/
|
|
40
|
+
newWindowHint?: string;
|
|
12
41
|
}
|
|
13
42
|
/**
|
|
14
43
|
* Presentational CTA renderer (REB-394, moved to `@reblu/site-next` in REB-418).
|
|
15
44
|
* Pure — takes already-resolved CTAs and renders one styled button-link each.
|
|
16
45
|
* Internal hrefs use `next/link` (client routing); external URLs open in a new,
|
|
17
|
-
* safely-rel tab
|
|
18
|
-
*
|
|
46
|
+
* safely-rel tab and carry a visually-hidden new-window hint. `analyticsId` is
|
|
47
|
+
* surfaced as `data-cta-id` on the clickable element for GA `cta_click`
|
|
48
|
+
* tracking. Renders nothing when there are no CTAs.
|
|
49
|
+
*
|
|
50
|
+
* The first CTA is the primary one; the rest are secondary. Each role takes its
|
|
51
|
+
* own Button variant and extra class, so a SITE maps the CTAs to its own button
|
|
52
|
+
* styles without forking this markup.
|
|
19
53
|
*
|
|
20
54
|
* Imports `Button` from `@reblu/site-ui` (public) — never `@reblu/ui` (private).
|
|
21
55
|
*/
|
|
22
|
-
declare function CtaButtons({ ctas, className, buttonClassName, size }: CtaButtonsProps): react_jsx_runtime.JSX.Element | null;
|
|
56
|
+
declare function CtaButtons({ ctas, className, buttonClassName, size, icon, primaryVariant, secondaryVariant, primaryClassName, secondaryClassName, newWindowHint, }: CtaButtonsProps): react_jsx_runtime.JSX.Element | null;
|
|
23
57
|
|
|
24
|
-
export { CtaButtons, type CtaButtonsProps };
|
|
58
|
+
export { type CtaButtonVariant, CtaButtons, type CtaButtonsProps };
|
package/dist/cta-buttons.js
CHANGED
|
@@ -1,25 +1,64 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/cta-buttons.tsx
|
|
4
|
-
import { Button } from "@reblu/site-ui";
|
|
5
|
-
import Link from "next/link";
|
|
6
|
-
import {
|
|
4
|
+
import { Button, cn } from "@reblu/site-ui";
|
|
5
|
+
import Link from "next/link.js";
|
|
6
|
+
import { cloneElement, Fragment, isValidElement } from "react";
|
|
7
|
+
import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
var DEFAULT_NEW_WINDOW_HINT = "(se abre en una nueva ventana)";
|
|
7
9
|
function isExternal(href) {
|
|
8
10
|
return /^https?:\/\//i.test(href);
|
|
9
11
|
}
|
|
10
|
-
function
|
|
12
|
+
function renderLeadingIcon(icon) {
|
|
13
|
+
if (icon == null || icon === false) return null;
|
|
14
|
+
if (isValidElement(icon) && icon.type !== Fragment) {
|
|
15
|
+
const element = icon;
|
|
16
|
+
return cloneElement(element, {
|
|
17
|
+
"aria-hidden": true,
|
|
18
|
+
className: cn(element.props.className, "shrink-0")
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "contents", children: icon });
|
|
22
|
+
}
|
|
23
|
+
function CtaButtons({
|
|
24
|
+
ctas,
|
|
25
|
+
className,
|
|
26
|
+
buttonClassName,
|
|
27
|
+
size = "lg",
|
|
28
|
+
icon,
|
|
29
|
+
primaryVariant = "default",
|
|
30
|
+
secondaryVariant = "default",
|
|
31
|
+
primaryClassName,
|
|
32
|
+
secondaryClassName,
|
|
33
|
+
newWindowHint = DEFAULT_NEW_WINDOW_HINT
|
|
34
|
+
}) {
|
|
11
35
|
if (ctas.length === 0) return null;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
36
|
+
const leadingIcon = renderLeadingIcon(icon);
|
|
37
|
+
return /* @__PURE__ */ jsx("div", { className, "data-slot": "cta-buttons", children: ctas.map((cta, index) => {
|
|
38
|
+
const isPrimary = index === 0;
|
|
39
|
+
return /* @__PURE__ */ jsx(
|
|
40
|
+
Button,
|
|
41
|
+
{
|
|
42
|
+
asChild: true,
|
|
43
|
+
size,
|
|
44
|
+
variant: isPrimary ? primaryVariant : secondaryVariant,
|
|
45
|
+
className: cn(buttonClassName, isPrimary ? primaryClassName : secondaryClassName),
|
|
46
|
+
"data-cta-id": cta.analyticsId ?? void 0,
|
|
47
|
+
children: isExternal(cta.href) ? /* @__PURE__ */ jsxs("a", { href: cta.href, target: "_blank", rel: "noopener noreferrer", children: [
|
|
48
|
+
leadingIcon,
|
|
49
|
+
cta.label,
|
|
50
|
+
newWindowHint ? /* @__PURE__ */ jsxs(Fragment2, { children: [
|
|
51
|
+
" ",
|
|
52
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: newWindowHint })
|
|
53
|
+
] }) : null
|
|
54
|
+
] }) : /* @__PURE__ */ jsxs(Link, { href: cta.href, children: [
|
|
55
|
+
leadingIcon,
|
|
56
|
+
cta.label
|
|
57
|
+
] })
|
|
58
|
+
},
|
|
59
|
+
cta.id
|
|
60
|
+
);
|
|
61
|
+
}) });
|
|
23
62
|
}
|
|
24
63
|
export {
|
|
25
64
|
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 { cloneElement, Fragment, isValidElement, type ReactElement, 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 marked\n * `aria-hidden`, so the accessible name stays the label. A custom icon\n * component must forward `aria-hidden` and `className` to its root element. An element icon (an\n * `<svg>` or an icon component) is rendered as a direct child of the button,\n * so shadcn's `has-[>svg]` icon padding still matches; anything else is\n * wrapped in an `aria-hidden` `display: contents` span.\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 * REB-930 — no wrapper around an element icon: `has-[>svg]:px-*` only matches an\n * svg that is a DIRECT child of the button. `display: contents` on the fallback\n * wrapper keeps it out of the flex layout, though a wrapped svg is still not a\n * direct child.\n */\nfunction renderLeadingIcon(icon: ReactNode): ReactNode {\n if (icon == null || icon === false) return null\n // A fragment cannot carry aria-hidden, so it takes the wrapper like a non-element.\n if (isValidElement(icon) && icon.type !== Fragment) {\n const element = icon as ReactElement<{ 'aria-hidden'?: boolean | 'true'; className?: string }>\n // shrink-0: the old wrapper kept any icon from shrinking in a narrow button;\n // shadcn's `[&_svg]:shrink-0` only covers svg.\n return cloneElement(element, {\n 'aria-hidden': true,\n className: cn(element.props.className, 'shrink-0'),\n })\n }\n return (\n <span aria-hidden='true' className='contents'>\n {icon}\n </span>\n )\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 = renderLeadingIcon(icon)\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 ? (\n <>\n {' '}\n <span className='sr-only'>{newWindowHint}</span>\n </>\n ) : 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;AACjB,SAAS,cAAc,UAAU,sBAAyD;AAoEtF,SAsDc,YAAAA,WAtDd,KAsDc,YAtDd;AA5DJ,IAAM,0BAA0B;AAqChC,SAAS,WAAW,MAAuB;AACzC,SAAO,gBAAgB,KAAK,IAAI;AAClC;AAQA,SAAS,kBAAkB,MAA4B;AACrD,MAAI,QAAQ,QAAQ,SAAS,MAAO,QAAO;AAE3C,MAAI,eAAe,IAAI,KAAK,KAAK,SAAS,UAAU;AAClD,UAAM,UAAU;AAGhB,WAAO,aAAa,SAAS;AAAA,MAC3B,eAAe;AAAA,MACf,WAAW,GAAG,QAAQ,MAAM,WAAW,UAAU;AAAA,IACnD,CAAC;AAAA,EACH;AACA,SACE,oBAAC,UAAK,eAAY,QAAO,WAAU,YAChC,gBACH;AAEJ;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,cAAc,kBAAkB,IAAI;AAE1C,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,gBACC,qBAAAA,WAAA,EACG;AAAA;AAAA,YACD,oBAAC,UAAK,WAAU,WAAW,yBAAc;AAAA,aAC3C,IACE;AAAA,WACN,IAEA,qBAAC,QAAK,MAAM,IAAI,MACb;AAAA;AAAA,UACA,IAAI;AAAA,WACP;AAAA;AAAA,MAtBG,IAAI;AAAA,IAwBX;AAAA,EAEJ,CAAC,GACH;AAEJ;","names":["Fragment"]}
|
package/dist/index.cjs
CHANGED
|
@@ -60,23 +60,62 @@ var NextCacheAdapter = class {
|
|
|
60
60
|
// src/cta-buttons.tsx
|
|
61
61
|
var import_site_ui = require("@reblu/site-ui");
|
|
62
62
|
var import_link = __toESM(require("next/link"), 1);
|
|
63
|
+
var import_react = require("react");
|
|
63
64
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
65
|
+
var DEFAULT_NEW_WINDOW_HINT = "(se abre en una nueva ventana)";
|
|
64
66
|
function isExternal(href) {
|
|
65
67
|
return /^https?:\/\//i.test(href);
|
|
66
68
|
}
|
|
67
|
-
function
|
|
69
|
+
function renderLeadingIcon(icon) {
|
|
70
|
+
if (icon == null || icon === false) return null;
|
|
71
|
+
if ((0, import_react.isValidElement)(icon) && icon.type !== import_react.Fragment) {
|
|
72
|
+
const element = icon;
|
|
73
|
+
return (0, import_react.cloneElement)(element, {
|
|
74
|
+
"aria-hidden": true,
|
|
75
|
+
className: (0, import_site_ui.cn)(element.props.className, "shrink-0")
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", className: "contents", children: icon });
|
|
79
|
+
}
|
|
80
|
+
function CtaButtons({
|
|
81
|
+
ctas,
|
|
82
|
+
className,
|
|
83
|
+
buttonClassName,
|
|
84
|
+
size = "lg",
|
|
85
|
+
icon,
|
|
86
|
+
primaryVariant = "default",
|
|
87
|
+
secondaryVariant = "default",
|
|
88
|
+
primaryClassName,
|
|
89
|
+
secondaryClassName,
|
|
90
|
+
newWindowHint = DEFAULT_NEW_WINDOW_HINT
|
|
91
|
+
}) {
|
|
68
92
|
if (ctas.length === 0) return null;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
const leadingIcon = renderLeadingIcon(icon);
|
|
94
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, "data-slot": "cta-buttons", children: ctas.map((cta, index) => {
|
|
95
|
+
const isPrimary = index === 0;
|
|
96
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
97
|
+
import_site_ui.Button,
|
|
98
|
+
{
|
|
99
|
+
asChild: true,
|
|
100
|
+
size,
|
|
101
|
+
variant: isPrimary ? primaryVariant : secondaryVariant,
|
|
102
|
+
className: (0, import_site_ui.cn)(buttonClassName, isPrimary ? primaryClassName : secondaryClassName),
|
|
103
|
+
"data-cta-id": cta.analyticsId ?? void 0,
|
|
104
|
+
children: isExternal(cta.href) ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", { href: cta.href, target: "_blank", rel: "noopener noreferrer", children: [
|
|
105
|
+
leadingIcon,
|
|
106
|
+
cta.label,
|
|
107
|
+
newWindowHint ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
108
|
+
" ",
|
|
109
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sr-only", children: newWindowHint })
|
|
110
|
+
] }) : null
|
|
111
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href: cta.href, children: [
|
|
112
|
+
leadingIcon,
|
|
113
|
+
cta.label
|
|
114
|
+
] })
|
|
115
|
+
},
|
|
116
|
+
cta.id
|
|
117
|
+
);
|
|
118
|
+
}) });
|
|
80
119
|
}
|
|
81
120
|
|
|
82
121
|
// src/sticky-mobile-cta.tsx
|
|
@@ -96,17 +135,10 @@ function StickyMobileCta({ ctas }) {
|
|
|
96
135
|
|
|
97
136
|
// src/editable-cta.tsx
|
|
98
137
|
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
|
-
}) {
|
|
138
|
+
async function EditableCta({ slot, client, fallback = null, ...buttonProps }) {
|
|
107
139
|
const { data: ctas } = await client.ctas.list(slot).catch(() => ({ data: [] }));
|
|
108
140
|
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,
|
|
141
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CtaButtons, { ctas, ...buttonProps });
|
|
110
142
|
}
|
|
111
143
|
// Annotate the CommonJS export names for ESM import in node:
|
|
112
144
|
0 && (module.exports = {
|