@reblu/site-blocks 0.33.0 → 0.35.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/dist/client.js CHANGED
@@ -2176,6 +2176,453 @@ function CourseGuestCheckoutForm({
2176
2176
  }
2177
2177
  );
2178
2178
  }
2179
+
2180
+ // src/components/reviews-carousel.tsx
2181
+ import { cn as cn16 } from "@reblu/site-ui/utils";
2182
+
2183
+ // src/components/_review-card.tsx
2184
+ import { cn as cn15 } from "@reblu/site-ui/utils";
2185
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2186
+ function isHttps(value) {
2187
+ if (typeof value !== "string") return false;
2188
+ try {
2189
+ const url = new URL(value);
2190
+ return url.protocol === "https:" && url.hostname !== "";
2191
+ } catch {
2192
+ return false;
2193
+ }
2194
+ }
2195
+ function isUsableReview(value) {
2196
+ if (typeof value !== "object" || value === null) return false;
2197
+ const review = value;
2198
+ return typeof review.id === "string" && typeof review.authorName === "string" && review.authorName.trim() !== "" && typeof review.rating === "number" && Number.isInteger(review.rating) && review.rating >= 1 && review.rating <= 5 && typeof review.sourceName === "string" && isHttps(review.sourceUrl);
2199
+ }
2200
+ function formatDay(day, locale) {
2201
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(day)) return null;
2202
+ const date = /* @__PURE__ */ new Date(`${day}T00:00:00.000Z`);
2203
+ if (Number.isNaN(date.getTime())) return null;
2204
+ return new Intl.DateTimeFormat(locale, { dateStyle: "long", timeZone: "UTC" }).format(date);
2205
+ }
2206
+ var STAR_PATH = "M12 2.5l2.9 5.9 6.5.9-4.7 4.6 1.1 6.5L12 17.3l-5.8 3.1 1.1-6.5L2.6 9.3l6.5-.9z";
2207
+ function Stars({ rating }) {
2208
+ return /* @__PURE__ */ jsx15("div", { role: "img", "aria-label": `${rating} de 5 estrellas`, className: "flex gap-0.5", children: [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ jsx15(
2209
+ "svg",
2210
+ {
2211
+ "aria-hidden": "true",
2212
+ viewBox: "0 0 24 24",
2213
+ width: "16",
2214
+ height: "16",
2215
+ "data-filled": n <= rating ? "true" : "false",
2216
+ className: n <= rating ? "fill-current" : "fill-none opacity-40",
2217
+ stroke: "currentColor",
2218
+ strokeWidth: "1.5",
2219
+ strokeLinejoin: "round",
2220
+ children: /* @__PURE__ */ jsx15("path", { d: STAR_PATH })
2221
+ },
2222
+ n
2223
+ )) });
2224
+ }
2225
+ function usableReviews(reviews) {
2226
+ return Array.isArray(reviews) ? reviews.filter(isUsableReview) : [];
2227
+ }
2228
+ function ReviewCard({
2229
+ review,
2230
+ linkLabel,
2231
+ newWindowLabel,
2232
+ locale,
2233
+ className
2234
+ }) {
2235
+ const date = formatDay(review.reviewDate, locale);
2236
+ return /* @__PURE__ */ jsxs13(
2237
+ "article",
2238
+ {
2239
+ "data-slot": "review",
2240
+ "data-source": review.source,
2241
+ className: cn15("flex h-full flex-col gap-3 rounded-lg border p-5", className),
2242
+ children: [
2243
+ /* @__PURE__ */ jsx15(Stars, { rating: review.rating }),
2244
+ review.text && review.text.trim() !== "" ? /* @__PURE__ */ jsx15("blockquote", { className: "flex-1", children: /* @__PURE__ */ jsx15("p", { children: review.text }) }) : null,
2245
+ /* @__PURE__ */ jsxs13("footer", { className: "mt-auto space-y-1 text-sm", children: [
2246
+ /* @__PURE__ */ jsx15("p", { className: "font-semibold", children: review.authorName }),
2247
+ date ? /* @__PURE__ */ jsx15("time", { dateTime: review.reviewDate, className: "block text-muted-foreground", children: date }) : null,
2248
+ /* @__PURE__ */ jsxs13(
2249
+ "a",
2250
+ {
2251
+ href: review.sourceUrl,
2252
+ target: "_blank",
2253
+ rel: "noopener nofollow",
2254
+ className: "inline-flex underline underline-offset-4",
2255
+ children: [
2256
+ linkLabel(review.sourceName),
2257
+ " ",
2258
+ /* @__PURE__ */ jsx15("span", { className: "sr-only", children: newWindowLabel })
2259
+ ]
2260
+ }
2261
+ )
2262
+ ] })
2263
+ ]
2264
+ }
2265
+ );
2266
+ }
2267
+
2268
+ // src/components/reviews-carousel.tsx
2269
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
2270
+ var DEFAULT_REVIEWS_CAROUSEL_LABELS = {
2271
+ region: "Rese\xF1as",
2272
+ roleDescription: "carrusel",
2273
+ previous: "Rese\xF1a anterior",
2274
+ next: "Rese\xF1a siguiente"
2275
+ };
2276
+ function ReviewsCarousel({
2277
+ reviews,
2278
+ heading,
2279
+ headingLevel = 2,
2280
+ notice,
2281
+ linkLabel = (name) => `Ver en ${name}`,
2282
+ newWindowLabel = "(se abre en una pesta\xF1a nueva)",
2283
+ locale = "es-ES",
2284
+ labels,
2285
+ className,
2286
+ headingClassName,
2287
+ itemClassName,
2288
+ cardClassName,
2289
+ noticeClassName
2290
+ }) {
2291
+ const usable = usableReviews(reviews);
2292
+ if (usable.length === 0) return null;
2293
+ const hasNotice = notice !== void 0 && notice !== null && notice !== "";
2294
+ return /* @__PURE__ */ jsxs14("div", { "data-slot": "reviews-carousel", className: cn16("space-y-4", className), children: [
2295
+ /* @__PURE__ */ jsx16(
2296
+ ProductCarousel,
2297
+ {
2298
+ products: usable,
2299
+ heading,
2300
+ headingLevel,
2301
+ headingClassName,
2302
+ labels: { ...DEFAULT_REVIEWS_CAROUSEL_LABELS, ...labels },
2303
+ itemClassName,
2304
+ renderProduct: (review) => /* @__PURE__ */ jsx16(
2305
+ ReviewCard,
2306
+ {
2307
+ review,
2308
+ linkLabel,
2309
+ newWindowLabel,
2310
+ locale,
2311
+ className: cardClassName
2312
+ }
2313
+ )
2314
+ }
2315
+ ),
2316
+ hasNotice ? /* @__PURE__ */ jsx16(
2317
+ "p",
2318
+ {
2319
+ "data-slot": "reviews-notice",
2320
+ className: slotClassName("", "text-sm text-muted-foreground", noticeClassName),
2321
+ children: notice
2322
+ }
2323
+ ) : null
2324
+ ] });
2325
+ }
2326
+
2327
+ // src/components/reviews.tsx
2328
+ import { cn as cn17 } from "@reblu/site-ui/utils";
2329
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
2330
+ function Reviews({
2331
+ reviews,
2332
+ heading,
2333
+ headingId = "reviews-heading",
2334
+ headingLevel = 2,
2335
+ notice,
2336
+ noticePosition = "below",
2337
+ linkLabel = (name) => `Ver en ${name}`,
2338
+ newWindowLabel = "(se abre en una pesta\xF1a nueva)",
2339
+ locale = "es-ES",
2340
+ className,
2341
+ headingClassName,
2342
+ gridClassName,
2343
+ cardClassName,
2344
+ noticeClassName
2345
+ }) {
2346
+ const usable = usableReviews(reviews);
2347
+ if (usable.length === 0) return null;
2348
+ const hasNotice = notice !== void 0 && notice !== null && notice !== "";
2349
+ const noticeNode = hasNotice ? /* @__PURE__ */ jsx17(
2350
+ "p",
2351
+ {
2352
+ "data-slot": "reviews-notice",
2353
+ className: slotClassName("", "text-sm text-muted-foreground", noticeClassName),
2354
+ children: notice
2355
+ }
2356
+ ) : null;
2357
+ const grid = /* @__PURE__ */ jsx17(
2358
+ "ul",
2359
+ {
2360
+ "data-slot": "reviews-grid",
2361
+ className: slotClassName("grid gap-6", "sm:grid-cols-2 lg:grid-cols-3", gridClassName),
2362
+ children: usable.map((review) => {
2363
+ return /* @__PURE__ */ jsx17("li", { children: /* @__PURE__ */ jsx17(
2364
+ ReviewCard,
2365
+ {
2366
+ review,
2367
+ linkLabel,
2368
+ newWindowLabel,
2369
+ locale,
2370
+ className: cardClassName
2371
+ }
2372
+ ) }, review.id);
2373
+ })
2374
+ }
2375
+ );
2376
+ const body = /* @__PURE__ */ jsxs15(Fragment5, { children: [
2377
+ noticePosition === "above" ? noticeNode : null,
2378
+ grid,
2379
+ noticePosition === "below" ? noticeNode : null
2380
+ ] });
2381
+ const hasHeading = heading !== void 0 && heading !== null && heading !== "";
2382
+ if (!hasHeading) {
2383
+ return /* @__PURE__ */ jsx17("div", { "data-slot": "reviews", className: cn17("space-y-4", className), children: body });
2384
+ }
2385
+ const Tag = `h${headingLevel}`;
2386
+ return /* @__PURE__ */ jsxs15("section", { "data-slot": "reviews", "aria-labelledby": headingId, className: cn17("space-y-4", className), children: [
2387
+ /* @__PURE__ */ jsx17(Tag, { id: headingId, className: slotClassName("", "text-xl font-semibold", headingClassName), children: heading }),
2388
+ body
2389
+ ] });
2390
+ }
2391
+
2392
+ // src/components/featured-reviews.tsx
2393
+ import { jsx as jsx18 } from "react/jsx-runtime";
2394
+ function FeaturedReviews({
2395
+ reviews,
2396
+ layout,
2397
+ heading,
2398
+ headingLevel,
2399
+ notice,
2400
+ linkLabel,
2401
+ locale,
2402
+ carouselLabels,
2403
+ className,
2404
+ cardClassName,
2405
+ gridClassName,
2406
+ itemClassName
2407
+ }) {
2408
+ if (layout === "carousel") {
2409
+ return /* @__PURE__ */ jsx18(
2410
+ ReviewsCarousel,
2411
+ {
2412
+ reviews,
2413
+ heading,
2414
+ headingLevel,
2415
+ notice,
2416
+ linkLabel,
2417
+ locale,
2418
+ labels: carouselLabels,
2419
+ className,
2420
+ cardClassName,
2421
+ itemClassName
2422
+ }
2423
+ );
2424
+ }
2425
+ return /* @__PURE__ */ jsx18(
2426
+ Reviews,
2427
+ {
2428
+ reviews,
2429
+ heading,
2430
+ headingLevel,
2431
+ notice,
2432
+ linkLabel,
2433
+ locale,
2434
+ className,
2435
+ cardClassName,
2436
+ gridClassName
2437
+ }
2438
+ );
2439
+ }
2440
+
2441
+ // src/components/reviews-block-renderer.tsx
2442
+ import { jsx as jsx19 } from "react/jsx-runtime";
2443
+ function ReviewsBlockRenderer({ block }) {
2444
+ const { layout, notice } = block.data;
2445
+ return /* @__PURE__ */ jsx19(
2446
+ FeaturedReviews,
2447
+ {
2448
+ reviews: block.reviews,
2449
+ layout: layout ?? "grid",
2450
+ notice: notice && notice.trim() !== "" ? notice : void 0,
2451
+ carouselLabels: { region: "Carrusel de rese\xF1as" }
2452
+ }
2453
+ );
2454
+ }
2455
+
2456
+ // src/components/team-directory.tsx
2457
+ import { useState as useState10 } from "react";
2458
+ import { cn as cn20 } from "@reblu/site-ui/utils";
2459
+
2460
+ // src/lib/page-links.ts
2461
+ function linkKey(link) {
2462
+ return `${link.category?.slug ?? ""}/${link.slug}`;
2463
+ }
2464
+ function linkFilterOptions(items) {
2465
+ const seen = /* @__PURE__ */ new Map();
2466
+ for (const item of items) {
2467
+ for (const link of item.links ?? []) {
2468
+ const key = linkKey(link);
2469
+ if (!seen.has(key)) seen.set(key, { key, label: link.title });
2470
+ }
2471
+ }
2472
+ return [...seen.values()];
2473
+ }
2474
+ function filterByLink(items, key) {
2475
+ if (key === null) return items;
2476
+ return items.filter((item) => (item.links ?? []).some((link) => linkKey(link) === key));
2477
+ }
2478
+
2479
+ // src/components/profile-card.tsx
2480
+ import { cn as cn19 } from "@reblu/site-ui/utils";
2481
+
2482
+ // src/components/professional-fields.tsx
2483
+ import { cn as cn18 } from "@reblu/site-ui/utils";
2484
+ import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
2485
+ function ProfessionalFields({
2486
+ fields,
2487
+ variant = "profile",
2488
+ className
2489
+ }) {
2490
+ const shown = (fields ?? []).filter((f) => variant !== "card" || f.showOnCard);
2491
+ if (shown.length === 0) return null;
2492
+ return /* @__PURE__ */ jsx20(
2493
+ "dl",
2494
+ {
2495
+ className: cn18(
2496
+ "grid gap-x-3 gap-y-1 text-sm",
2497
+ variant === "card" ? "justify-center" : "grid-cols-[auto_1fr]",
2498
+ className
2499
+ ),
2500
+ "data-slot": "professional-fields",
2501
+ "data-variant": variant,
2502
+ children: shown.map((field, index) => /* @__PURE__ */ jsxs16(
2503
+ "div",
2504
+ {
2505
+ className: variant === "card" ? "flex justify-center gap-1" : "contents",
2506
+ children: [
2507
+ /* @__PURE__ */ jsx20("dt", { className: "text-muted-foreground", children: field.label }),
2508
+ /* @__PURE__ */ jsx20("dd", { className: "m-0 font-medium", children: field.value })
2509
+ ]
2510
+ },
2511
+ `${field.label}-${index}`
2512
+ ))
2513
+ }
2514
+ );
2515
+ }
2516
+
2517
+ // src/components/profile-card.tsx
2518
+ import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
2519
+ function initials(name) {
2520
+ return name.split(/\s+/).filter(Boolean).slice(0, 2).map((part) => part[0]?.toUpperCase() ?? "").join("");
2521
+ }
2522
+ function ProfileCard({
2523
+ name,
2524
+ role,
2525
+ imageUrl,
2526
+ specialties = [],
2527
+ bio,
2528
+ fields,
2529
+ href,
2530
+ className
2531
+ }) {
2532
+ const NameTag = href ? "a" : "span";
2533
+ return /* @__PURE__ */ jsxs17(
2534
+ "article",
2535
+ {
2536
+ className: cn19(
2537
+ "flex flex-col items-center gap-3 rounded-lg border bg-card p-6 text-center",
2538
+ className
2539
+ ),
2540
+ "data-slot": "profile-card",
2541
+ children: [
2542
+ /* @__PURE__ */ jsx21("div", { className: "relative h-24 w-24 overflow-hidden rounded-full bg-muted", children: imageUrl ? /* @__PURE__ */ jsx21("img", { src: imageUrl, alt: name, loading: "lazy", className: "h-full w-full object-cover" }) : /* @__PURE__ */ jsx21(
2543
+ "span",
2544
+ {
2545
+ className: "flex h-full w-full items-center justify-center text-2xl font-semibold text-muted-foreground",
2546
+ "aria-hidden": "true",
2547
+ children: initials(name)
2548
+ }
2549
+ ) }),
2550
+ /* @__PURE__ */ jsxs17("div", { className: "space-y-1", children: [
2551
+ /* @__PURE__ */ jsx21("h3", { className: "text-lg font-semibold leading-tight", children: /* @__PURE__ */ jsx21(NameTag, { ...href ? { href, className: "transition-colors hover:text-primary" } : {}, children: name }) }),
2552
+ role && /* @__PURE__ */ jsx21("p", { className: "text-sm text-muted-foreground", children: role })
2553
+ ] }),
2554
+ /* @__PURE__ */ jsx21(ProfessionalFields, { fields, variant: "card" }),
2555
+ specialties.length > 0 && /* @__PURE__ */ jsx21("ul", { className: "flex flex-wrap justify-center gap-1.5 p-0", children: specialties.map((s) => /* @__PURE__ */ jsx21(
2556
+ "li",
2557
+ {
2558
+ className: "inline-flex items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground",
2559
+ children: s
2560
+ },
2561
+ s
2562
+ )) }),
2563
+ bio && /* @__PURE__ */ jsx21("p", { className: "text-sm text-muted-foreground", children: bio })
2564
+ ]
2565
+ }
2566
+ );
2567
+ }
2568
+
2569
+ // src/components/team-directory.tsx
2570
+ import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
2571
+ var DEFAULT_TEAM_DIRECTORY_LABELS = {
2572
+ all: "Todos",
2573
+ filter: "Filtrar el equipo"
2574
+ };
2575
+ var CHIP_CLASS = "inline-flex cursor-pointer items-center rounded-full border px-3 py-1 text-sm font-medium transition-colors aria-pressed:bg-primary aria-pressed:text-primary-foreground aria-pressed:border-primary hover:bg-muted";
2576
+ function TeamDirectory({
2577
+ members,
2578
+ memberHref,
2579
+ memberHrefBase,
2580
+ showLinksOnCards = false,
2581
+ labels,
2582
+ className
2583
+ }) {
2584
+ const t = { ...DEFAULT_TEAM_DIRECTORY_LABELS, ...labels };
2585
+ const [selected, setSelected] = useState10(null);
2586
+ if (members.length === 0) return null;
2587
+ const options = linkFilterOptions(members);
2588
+ const visible = filterByLink(members, selected);
2589
+ return /* @__PURE__ */ jsxs18("div", { className: cn20("space-y-6", className), "data-slot": "team-directory", children: [
2590
+ options.length > 0 && /* @__PURE__ */ jsxs18("div", { role: "group", "aria-label": t.filter, className: "flex flex-wrap gap-2", children: [
2591
+ /* @__PURE__ */ jsx22(
2592
+ "button",
2593
+ {
2594
+ type: "button",
2595
+ className: CHIP_CLASS,
2596
+ "aria-pressed": selected === null,
2597
+ onClick: () => setSelected(null),
2598
+ children: t.all
2599
+ }
2600
+ ),
2601
+ options.map((option) => /* @__PURE__ */ jsx22(
2602
+ "button",
2603
+ {
2604
+ type: "button",
2605
+ className: CHIP_CLASS,
2606
+ "aria-pressed": selected === option.key,
2607
+ onClick: () => setSelected(option.key),
2608
+ children: option.label
2609
+ },
2610
+ option.key
2611
+ ))
2612
+ ] }),
2613
+ /* @__PURE__ */ jsx22("ul", { className: "grid list-none grid-cols-1 gap-6 p-0 sm:grid-cols-2 lg:grid-cols-3", children: visible.map((member) => /* @__PURE__ */ jsx22("li", { children: /* @__PURE__ */ jsx22(
2614
+ ProfileCard,
2615
+ {
2616
+ name: member.title,
2617
+ role: member.professional?.role ?? void 0,
2618
+ imageUrl: member.coverImageUrl ?? void 0,
2619
+ specialties: showLinksOnCards ? (member.links ?? []).map((l) => l.title) : [],
2620
+ href: memberHref ? memberHref(member) : memberHrefBase !== void 0 ? `${memberHrefBase}/${member.slug}` : void 0,
2621
+ className: "h-full"
2622
+ }
2623
+ ) }, linkKey(member))) })
2624
+ ] });
2625
+ }
2179
2626
  export {
2180
2627
  AddToCartButton,
2181
2628
  CartIcon,
@@ -2189,10 +2636,16 @@ export {
2189
2636
  DEFAULT_COURSE_SYLLABUS_LABELS,
2190
2637
  DEFAULT_DATA_REQUEST_FORM_LABELS,
2191
2638
  DEFAULT_PRODUCT_CAROUSEL_LABELS,
2639
+ DEFAULT_REVIEWS_CAROUSEL_LABELS,
2640
+ DEFAULT_TEAM_DIRECTORY_LABELS,
2192
2641
  DEFAULT_WITHDRAWAL_REQUEST_FORM_LABELS,
2193
2642
  DataRequestForm,
2643
+ FeaturedReviews,
2194
2644
  ProductCarousel,
2645
+ ReviewsBlockRenderer,
2646
+ ReviewsCarousel,
2195
2647
  ShareButtons,
2648
+ TeamDirectory,
2196
2649
  WITHDRAWAL_REQUEST_FIELD_LIMITS,
2197
2650
  WithdrawalRequestForm,
2198
2651
  useCollection