@reblu/site-blocks 0.19.0 → 0.21.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 +29 -0
- package/README.md +126 -2
- package/dist/client.cjs +372 -45
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +78 -2
- package/dist/client.d.ts +78 -2
- package/dist/client.js +369 -44
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +440 -305
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -4
- package/dist/index.d.ts +68 -4
- package/dist/index.js +438 -305
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
-
import { ShopProductListItem, DataRequestType, DataRequestInput, DataRequestSubmission } from '@reblu/site-contracts';
|
|
4
|
+
import { ShopProductListItem, AcademyModulePublic, AcademyLessonPublic, DataRequestType, DataRequestInput, DataRequestSubmission } from '@reblu/site-contracts';
|
|
5
5
|
export { DATA_REQUEST_TYPES } from '@reblu/site-contracts';
|
|
6
6
|
import { H as HeadingLevel } from './_heading-BsNbc1wc.js';
|
|
7
7
|
|
|
@@ -305,6 +305,82 @@ declare function ProductCarousel<T extends {
|
|
|
305
305
|
id: string | number;
|
|
306
306
|
} = ShopProductListItem>(props: ProductCarouselProps<T>): react_jsx_runtime.JSX.Element | null;
|
|
307
307
|
|
|
308
|
+
/**
|
|
309
|
+
* `CourseSyllabus` — the drop-in programme of a public course page, over
|
|
310
|
+
* `AcademyCourseDetail.modules` (`client.academy.bySlug()`), REB-1134; redesigned
|
|
311
|
+
* in REB-1135.
|
|
312
|
+
*
|
|
313
|
+
* - The header reads the heading, then «N módulos · M lecciones · total», and
|
|
314
|
+
* carries an «Expandir todo / Contraer todo» control.
|
|
315
|
+
* - Each module with lessons is a `<button>` with `aria-expanded` and
|
|
316
|
+
* `aria-controls`, showing its lesson count and, when every lesson has one,
|
|
317
|
+
* the module's total duration, with a chevron that rotates when open.
|
|
318
|
+
* - Each lesson row shows a type icon (video / reading, named for screen
|
|
319
|
+
* readers), its title, its duration as «m:ss» / «h:mm:ss», and — on a
|
|
320
|
+
* free-preview lesson — «Vista previa gratuita» as a call to action:
|
|
321
|
+
* a link (`getPreviewHref` / `previewHrefTemplate`), a button (`onPreview`),
|
|
322
|
+
* or a plain label when the SITE wires neither.
|
|
323
|
+
* - A module without lessons is a plain row (title only), never a button.
|
|
324
|
+
*
|
|
325
|
+
* It renders only what the API serves: published lessons, no video, playback
|
|
326
|
+
* or attachment data. The preview itself is fetched by the page it links to
|
|
327
|
+
* (`getLessonPreview` in `@reblu/site-next/academy-server`).
|
|
328
|
+
*
|
|
329
|
+
* Client Component: it holds the open/closed state. Copy and
|
|
330
|
+
* `previewHrefTemplate` are serialisable, so a Server Component page can render
|
|
331
|
+
* it directly; `getPreviewHref` / `onPreview` are functions, so they need a
|
|
332
|
+
* Client Component parent.
|
|
333
|
+
*/
|
|
334
|
+
interface CourseSyllabusLabels {
|
|
335
|
+
heading: string;
|
|
336
|
+
moduleSingular: string;
|
|
337
|
+
modulePlural: string;
|
|
338
|
+
lessonSingular: string;
|
|
339
|
+
lessonPlural: string;
|
|
340
|
+
/** Call to action (or label) on a lesson with `isFreePreview`. */
|
|
341
|
+
freePreview: string;
|
|
342
|
+
/** Hours unit in module/course totals («1 h 15 min»). */
|
|
343
|
+
hours: string;
|
|
344
|
+
/** Minutes unit in module/course totals. */
|
|
345
|
+
minutes: string;
|
|
346
|
+
expandAll: string;
|
|
347
|
+
collapseAll: string;
|
|
348
|
+
/** Screen-reader name of the video lesson icon. */
|
|
349
|
+
videoLesson: string;
|
|
350
|
+
/** Screen-reader name of the text (reading) lesson icon. */
|
|
351
|
+
textLesson: string;
|
|
352
|
+
}
|
|
353
|
+
declare const DEFAULT_COURSE_SYLLABUS_LABELS: CourseSyllabusLabels;
|
|
354
|
+
/** A module as served. `lessons` is optional so an older payload still renders. */
|
|
355
|
+
type CourseSyllabusModule = Pick<AcademyModulePublic, 'id' | 'title'> & {
|
|
356
|
+
description?: string | null;
|
|
357
|
+
lessons?: readonly AcademyLessonPublic[];
|
|
358
|
+
};
|
|
359
|
+
interface CourseSyllabusProps {
|
|
360
|
+
/** `AcademyCourseDetail.modules`, in the order the API returns them. */
|
|
361
|
+
modules: readonly CourseSyllabusModule[];
|
|
362
|
+
labels?: Partial<CourseSyllabusLabels>;
|
|
363
|
+
/** Level of the heading. Default `2`. */
|
|
364
|
+
headingLevel?: HeadingLevel;
|
|
365
|
+
/** Replaces the heading's default typography (see the className rule in the README). */
|
|
366
|
+
headingClassName?: string;
|
|
367
|
+
className?: string;
|
|
368
|
+
/**
|
|
369
|
+
* Link target of a free-preview lesson, e.g. `/cursos/${slug}/preview/${l.slug}`.
|
|
370
|
+
* Called only for lessons with `isFreePreview`. Return `undefined` to keep the label.
|
|
371
|
+
*/
|
|
372
|
+
getPreviewHref?: (lesson: AcademyLessonPublic) => string | undefined;
|
|
373
|
+
/**
|
|
374
|
+
* Serialisable alternative to `getPreviewHref` for Server Component pages:
|
|
375
|
+
* `{lessonSlug}` and `{lessonId}` are replaced (URL-encoded). Ignored when
|
|
376
|
+
* `getPreviewHref` is given.
|
|
377
|
+
*/
|
|
378
|
+
previewHrefTemplate?: string;
|
|
379
|
+
/** Renders the call to action as a `<button>` (e.g. to open a modal). Used when no href resolves. */
|
|
380
|
+
onPreview?: (lesson: AcademyLessonPublic) => void;
|
|
381
|
+
}
|
|
382
|
+
declare function CourseSyllabus({ modules, labels: labelOverrides, headingLevel, headingClassName, className, getPreviewHref, previewHrefTemplate, onPreview, }: CourseSyllabusProps): react_jsx_runtime.JSX.Element | null;
|
|
383
|
+
|
|
308
384
|
/**
|
|
309
385
|
* `DataRequestForm` — drop-in public GDPR rights form (REB-980) over
|
|
310
386
|
* `POST /api/site/data-requests` (REB-77). The visitor picks the right they
|
|
@@ -384,4 +460,4 @@ interface DataRequestFormProps {
|
|
|
384
460
|
}
|
|
385
461
|
declare function DataRequestForm({ getCaptchaToken, submit, endpoint, onInteract, onSubmitted, requestTypes, labels, locale, className, }: DataRequestFormProps): react_jsx_runtime.JSX.Element;
|
|
386
462
|
|
|
387
|
-
export { AddToCartButton, type AddToCartButtonLabels, type AddToCartButtonProps, type AddToCartButtonStatus, CartIcon, type CartIconLabels, type CartIconProps, Collection, type CollectionFacet, type CollectionFacetOption, type CollectionFilter, type CollectionLabels, type CollectionProps, DATA_REQUEST_FIELD_LIMITS, DEFAULT_DATA_REQUEST_FORM_LABELS, DEFAULT_PRODUCT_CAROUSEL_LABELS, DataRequestForm, type DataRequestFormLabels, type DataRequestFormProps, type DataRequestSubmitResult, ProductCarousel, type ProductCarouselCustomCardProps, type ProductCarouselDefaultCardProps, type ProductCarouselLabels, type ProductCarouselProps, ShareButtons, type ShareButtonsLabels, type ShareButtonsProps, type UseCollectionOptions, type UseCollectionResult, useCollection };
|
|
463
|
+
export { AddToCartButton, type AddToCartButtonLabels, type AddToCartButtonProps, type AddToCartButtonStatus, CartIcon, type CartIconLabels, type CartIconProps, Collection, type CollectionFacet, type CollectionFacetOption, type CollectionFilter, type CollectionLabels, type CollectionProps, CourseSyllabus, type CourseSyllabusLabels, type CourseSyllabusModule, type CourseSyllabusProps, DATA_REQUEST_FIELD_LIMITS, DEFAULT_COURSE_SYLLABUS_LABELS, DEFAULT_DATA_REQUEST_FORM_LABELS, DEFAULT_PRODUCT_CAROUSEL_LABELS, DataRequestForm, type DataRequestFormLabels, type DataRequestFormProps, type DataRequestSubmitResult, ProductCarousel, type ProductCarouselCustomCardProps, type ProductCarouselDefaultCardProps, type ProductCarouselLabels, type ProductCarouselProps, ShareButtons, type ShareButtonsLabels, type ShareButtonsProps, type UseCollectionOptions, type UseCollectionResult, useCollection };
|
package/dist/client.js
CHANGED
|
@@ -415,7 +415,7 @@ import { cn as cn4 } from "@reblu/site-ui/utils";
|
|
|
415
415
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
416
416
|
var DEFAULT_LABELS4 = {
|
|
417
417
|
cart: "Carrito",
|
|
418
|
-
itemsSuffix: (
|
|
418
|
+
itemsSuffix: (count2) => `${count2} art\xEDculo${count2 === 1 ? "" : "s"}`
|
|
419
419
|
};
|
|
420
420
|
var BUMP_DURATION_MS = 400;
|
|
421
421
|
function CartIcon({ itemCount, href, hydrated, bumpOnAdd, labels, className }) {
|
|
@@ -687,13 +687,336 @@ function ProductCarousel(props) {
|
|
|
687
687
|
] });
|
|
688
688
|
}
|
|
689
689
|
|
|
690
|
-
// src/components/
|
|
691
|
-
import {
|
|
690
|
+
// src/components/course-syllabus.tsx
|
|
691
|
+
import { useId as useId2, useState as useState6 } from "react";
|
|
692
|
+
import { cn as cn8 } from "@reblu/site-ui/utils";
|
|
693
|
+
|
|
694
|
+
// src/lib/slot-class-name.ts
|
|
692
695
|
import { cn as cn7 } from "@reblu/site-ui/utils";
|
|
696
|
+
var MERGE_SAFE_TEXT = /(?:^|:)!?text-(?:left|center|right|justify|start|end|wrap|nowrap|balance|pretty|ellipsis|clip)$/;
|
|
697
|
+
var TEXT_UTILITY = /(?:^|:)!?-?text-/;
|
|
698
|
+
function bypassesMerge(token) {
|
|
699
|
+
return TEXT_UTILITY.test(token) && !MERGE_SAFE_TEXT.test(token);
|
|
700
|
+
}
|
|
701
|
+
function slotClassName(structural, defaults, consumer) {
|
|
702
|
+
if (consumer === void 0 || consumer.trim() === "") return cn7(structural, defaults);
|
|
703
|
+
const tokens = consumer.split(/\s+/).filter(Boolean);
|
|
704
|
+
const verbatim = tokens.filter(bypassesMerge);
|
|
705
|
+
const merged = cn7(structural, tokens.filter((token) => !bypassesMerge(token)));
|
|
706
|
+
return [merged, ...verbatim].filter(Boolean).join(" ");
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// src/components/course-syllabus.tsx
|
|
710
|
+
import { Fragment, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
711
|
+
var DEFAULT_COURSE_SYLLABUS_LABELS = {
|
|
712
|
+
heading: "Programa",
|
|
713
|
+
moduleSingular: "m\xF3dulo",
|
|
714
|
+
modulePlural: "m\xF3dulos",
|
|
715
|
+
lessonSingular: "lecci\xF3n",
|
|
716
|
+
lessonPlural: "lecciones",
|
|
717
|
+
freePreview: "Vista previa gratuita",
|
|
718
|
+
hours: "h",
|
|
719
|
+
minutes: "min",
|
|
720
|
+
expandAll: "Expandir todo",
|
|
721
|
+
collapseAll: "Contraer todo",
|
|
722
|
+
videoLesson: "V\xEDdeo",
|
|
723
|
+
textLesson: "Lectura"
|
|
724
|
+
};
|
|
725
|
+
var SEPARATOR = " \xB7 ";
|
|
726
|
+
function count(n, singular, plural) {
|
|
727
|
+
return `${n} ${n === 1 ? singular : plural}`;
|
|
728
|
+
}
|
|
729
|
+
function formatTotal(seconds, labels) {
|
|
730
|
+
const totalMinutes = Math.max(1, Math.round(seconds / 60));
|
|
731
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
732
|
+
const minutes = totalMinutes % 60;
|
|
733
|
+
if (hours === 0) return `${minutes} ${labels.minutes}`;
|
|
734
|
+
return minutes === 0 ? `${hours} ${labels.hours}` : `${hours} ${labels.hours} ${minutes} ${labels.minutes}`;
|
|
735
|
+
}
|
|
736
|
+
function splitSeconds(seconds) {
|
|
737
|
+
const whole = Math.round(seconds);
|
|
738
|
+
return [Math.floor(whole / 3600), Math.floor(whole % 3600 / 60), whole % 60];
|
|
739
|
+
}
|
|
740
|
+
function formatClock(seconds) {
|
|
741
|
+
const [h, m, s] = splitSeconds(seconds);
|
|
742
|
+
const ss = String(s).padStart(2, "0");
|
|
743
|
+
return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
|
|
744
|
+
}
|
|
745
|
+
function isoDuration(seconds) {
|
|
746
|
+
const [h, m, s] = splitSeconds(seconds);
|
|
747
|
+
return `PT${h > 0 ? `${h}H` : ""}${m}M${s}S`;
|
|
748
|
+
}
|
|
749
|
+
function hasDuration(lesson) {
|
|
750
|
+
return typeof lesson.durationSeconds === "number" && lesson.durationSeconds > 0;
|
|
751
|
+
}
|
|
752
|
+
function totalSeconds(lessons) {
|
|
753
|
+
if (lessons.length === 0) return null;
|
|
754
|
+
let total = 0;
|
|
755
|
+
for (const l of lessons) {
|
|
756
|
+
if (!hasDuration(l)) return null;
|
|
757
|
+
total += l.durationSeconds;
|
|
758
|
+
}
|
|
759
|
+
return total;
|
|
760
|
+
}
|
|
761
|
+
function fillTemplate(template, lesson) {
|
|
762
|
+
return template.replaceAll("{lessonSlug}", encodeURIComponent(lesson.slug)).replaceAll("{lessonId}", encodeURIComponent(lesson.id));
|
|
763
|
+
}
|
|
764
|
+
var ICON_PROPS = {
|
|
765
|
+
"aria-hidden": true,
|
|
766
|
+
viewBox: "0 0 24 24",
|
|
767
|
+
fill: "none",
|
|
768
|
+
stroke: "currentColor",
|
|
769
|
+
strokeWidth: 2,
|
|
770
|
+
strokeLinecap: "round",
|
|
771
|
+
strokeLinejoin: "round"
|
|
772
|
+
};
|
|
773
|
+
function LessonIcon({ type }) {
|
|
774
|
+
return /* @__PURE__ */ jsx8(
|
|
775
|
+
"svg",
|
|
776
|
+
{
|
|
777
|
+
...ICON_PROPS,
|
|
778
|
+
"data-slot": "course-syllabus-lesson-icon",
|
|
779
|
+
"data-type": type,
|
|
780
|
+
className: "size-4 shrink-0 text-muted-foreground",
|
|
781
|
+
children: type === "video" ? /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
782
|
+
/* @__PURE__ */ jsx8("circle", { cx: "12", cy: "12", r: "9" }),
|
|
783
|
+
/* @__PURE__ */ jsx8("path", { d: "m10 8.5 5 3.5-5 3.5z" })
|
|
784
|
+
] }) : /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
785
|
+
/* @__PURE__ */ jsx8("path", { d: "M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" }),
|
|
786
|
+
/* @__PURE__ */ jsx8("path", { d: "M14 3v5h5M9 13h6M9 17h4" })
|
|
787
|
+
] })
|
|
788
|
+
}
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
function PlayIcon() {
|
|
792
|
+
return /* @__PURE__ */ jsx8("svg", { ...ICON_PROPS, className: "size-3.5 shrink-0", children: /* @__PURE__ */ jsx8("path", { d: "m7 4 13 8-13 8z", fill: "currentColor" }) });
|
|
793
|
+
}
|
|
794
|
+
var PREVIEW_CTA = "inline-flex items-center gap-1.5 rounded-full border border-primary/40 bg-primary/10 px-3 py-1 text-xs font-semibold text-primary transition-colors hover:bg-primary hover:text-primary-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring motion-reduce:transition-none";
|
|
795
|
+
function CourseSyllabus({
|
|
796
|
+
modules,
|
|
797
|
+
labels: labelOverrides,
|
|
798
|
+
headingLevel = 2,
|
|
799
|
+
headingClassName,
|
|
800
|
+
className,
|
|
801
|
+
getPreviewHref,
|
|
802
|
+
previewHrefTemplate,
|
|
803
|
+
onPreview
|
|
804
|
+
}) {
|
|
805
|
+
const labels = { ...DEFAULT_COURSE_SYLLABUS_LABELS, ...labelOverrides };
|
|
806
|
+
const baseId = useId2();
|
|
807
|
+
const [open, setOpen] = useState6(() => /* @__PURE__ */ new Set());
|
|
808
|
+
if (modules.length === 0) return null;
|
|
809
|
+
const expandable = modules.filter((m) => (m.lessons?.length ?? 0) > 0).map((m) => m.id);
|
|
810
|
+
const allLessons = modules.flatMap((m) => m.lessons ?? []);
|
|
811
|
+
const courseSeconds = totalSeconds(allLessons);
|
|
812
|
+
const summary = [count(modules.length, labels.moduleSingular, labels.modulePlural)];
|
|
813
|
+
if (allLessons.length > 0)
|
|
814
|
+
summary.push(count(allLessons.length, labels.lessonSingular, labels.lessonPlural));
|
|
815
|
+
if (courseSeconds !== null) summary.push(formatTotal(courseSeconds, labels));
|
|
816
|
+
const allOpen = expandable.length > 0 && expandable.every((id) => open.has(id));
|
|
817
|
+
const headingId = `${baseId}-heading`;
|
|
818
|
+
const Tag = `h${headingLevel}`;
|
|
819
|
+
function toggle(id) {
|
|
820
|
+
setOpen((prev) => {
|
|
821
|
+
const next = new Set(prev);
|
|
822
|
+
if (next.has(id)) next.delete(id);
|
|
823
|
+
else next.add(id);
|
|
824
|
+
return next;
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
function previewHref(lesson) {
|
|
828
|
+
if (getPreviewHref) return getPreviewHref(lesson);
|
|
829
|
+
return previewHrefTemplate ? fillTemplate(previewHrefTemplate, lesson) : void 0;
|
|
830
|
+
}
|
|
831
|
+
function renderPreview(lesson) {
|
|
832
|
+
const name = `${labels.freePreview}: ${lesson.title}`;
|
|
833
|
+
const href = previewHref(lesson);
|
|
834
|
+
if (href) {
|
|
835
|
+
return /* @__PURE__ */ jsxs6("a", { href, "aria-label": name, "data-slot": "course-syllabus-preview", className: PREVIEW_CTA, children: [
|
|
836
|
+
/* @__PURE__ */ jsx8(PlayIcon, {}),
|
|
837
|
+
labels.freePreview
|
|
838
|
+
] });
|
|
839
|
+
}
|
|
840
|
+
if (onPreview) {
|
|
841
|
+
return /* @__PURE__ */ jsxs6(
|
|
842
|
+
"button",
|
|
843
|
+
{
|
|
844
|
+
type: "button",
|
|
845
|
+
"aria-label": name,
|
|
846
|
+
"data-slot": "course-syllabus-preview",
|
|
847
|
+
onClick: () => onPreview(lesson),
|
|
848
|
+
className: cn8(PREVIEW_CTA, "cursor-pointer"),
|
|
849
|
+
children: [
|
|
850
|
+
/* @__PURE__ */ jsx8(PlayIcon, {}),
|
|
851
|
+
labels.freePreview
|
|
852
|
+
]
|
|
853
|
+
}
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
return /* @__PURE__ */ jsx8(
|
|
857
|
+
"span",
|
|
858
|
+
{
|
|
859
|
+
"data-slot": "course-syllabus-preview",
|
|
860
|
+
className: "rounded-full bg-muted px-2.5 py-0.5 text-xs font-medium text-muted-foreground",
|
|
861
|
+
children: labels.freePreview
|
|
862
|
+
}
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
return /* @__PURE__ */ jsxs6(
|
|
866
|
+
"section",
|
|
867
|
+
{
|
|
868
|
+
"data-slot": "course-syllabus",
|
|
869
|
+
"aria-labelledby": headingId,
|
|
870
|
+
className: cn8("space-y-4", className),
|
|
871
|
+
children: [
|
|
872
|
+
/* @__PURE__ */ jsxs6(
|
|
873
|
+
"div",
|
|
874
|
+
{
|
|
875
|
+
"data-slot": "course-syllabus-header",
|
|
876
|
+
className: "flex flex-wrap items-end justify-between gap-x-4 gap-y-2",
|
|
877
|
+
children: [
|
|
878
|
+
/* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
|
|
879
|
+
/* @__PURE__ */ jsx8(
|
|
880
|
+
Tag,
|
|
881
|
+
{
|
|
882
|
+
id: headingId,
|
|
883
|
+
className: slotClassName("", "text-xl font-semibold", headingClassName),
|
|
884
|
+
children: labels.heading
|
|
885
|
+
}
|
|
886
|
+
),
|
|
887
|
+
/* @__PURE__ */ jsx8("p", { "data-slot": "course-syllabus-summary", className: "text-sm text-muted-foreground", children: summary.join(SEPARATOR) })
|
|
888
|
+
] }),
|
|
889
|
+
expandable.length > 0 && /* @__PURE__ */ jsx8(
|
|
890
|
+
"button",
|
|
891
|
+
{
|
|
892
|
+
type: "button",
|
|
893
|
+
"data-slot": "course-syllabus-toggle-all",
|
|
894
|
+
onClick: () => setOpen(allOpen ? /* @__PURE__ */ new Set() : new Set(expandable)),
|
|
895
|
+
className: "cursor-pointer rounded-md px-2 py-1 text-sm font-semibold text-primary underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
|
|
896
|
+
children: allOpen ? labels.collapseAll : labels.expandAll
|
|
897
|
+
}
|
|
898
|
+
)
|
|
899
|
+
]
|
|
900
|
+
}
|
|
901
|
+
),
|
|
902
|
+
/* @__PURE__ */ jsx8("ol", { className: "list-none divide-y divide-border overflow-hidden rounded-xl border border-border bg-card p-0", children: modules.map((module, index) => {
|
|
903
|
+
const lessons = module.lessons ?? [];
|
|
904
|
+
const number = /* @__PURE__ */ jsx8(
|
|
905
|
+
"span",
|
|
906
|
+
{
|
|
907
|
+
"aria-hidden": "true",
|
|
908
|
+
className: "flex size-8 shrink-0 items-center justify-center rounded-full bg-primary/10 text-sm font-semibold text-primary tabular-nums",
|
|
909
|
+
children: index + 1
|
|
910
|
+
}
|
|
911
|
+
);
|
|
912
|
+
if (lessons.length === 0) {
|
|
913
|
+
return /* @__PURE__ */ jsxs6(
|
|
914
|
+
"li",
|
|
915
|
+
{
|
|
916
|
+
"data-slot": "course-syllabus-module",
|
|
917
|
+
className: "flex items-center gap-4 px-4 py-4 sm:px-5",
|
|
918
|
+
children: [
|
|
919
|
+
number,
|
|
920
|
+
/* @__PURE__ */ jsx8("span", { className: "font-semibold leading-snug", children: module.title })
|
|
921
|
+
]
|
|
922
|
+
},
|
|
923
|
+
module.id
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
const isOpen = open.has(module.id);
|
|
927
|
+
const panelId = `${baseId}-module-${index}`;
|
|
928
|
+
const seconds = totalSeconds(lessons);
|
|
929
|
+
const meta = [count(lessons.length, labels.lessonSingular, labels.lessonPlural)];
|
|
930
|
+
if (seconds !== null) meta.push(formatTotal(seconds, labels));
|
|
931
|
+
return /* @__PURE__ */ jsxs6("li", { "data-slot": "course-syllabus-module", children: [
|
|
932
|
+
/* @__PURE__ */ jsxs6(
|
|
933
|
+
"button",
|
|
934
|
+
{
|
|
935
|
+
type: "button",
|
|
936
|
+
"aria-expanded": isOpen,
|
|
937
|
+
"aria-controls": panelId,
|
|
938
|
+
onClick: () => toggle(module.id),
|
|
939
|
+
className: cn8(
|
|
940
|
+
"flex w-full cursor-pointer items-center gap-4 px-4 py-4 text-left transition-colors hover:bg-muted/60 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring motion-reduce:transition-none sm:px-5",
|
|
941
|
+
isOpen && "bg-muted/40"
|
|
942
|
+
),
|
|
943
|
+
children: [
|
|
944
|
+
number,
|
|
945
|
+
/* @__PURE__ */ jsxs6("span", { className: "flex min-w-0 flex-1 flex-col gap-0.5", children: [
|
|
946
|
+
/* @__PURE__ */ jsx8("span", { className: "font-semibold leading-snug", children: module.title }),
|
|
947
|
+
/* @__PURE__ */ jsx8("span", { className: "text-sm text-muted-foreground", children: meta.join(SEPARATOR) })
|
|
948
|
+
] }),
|
|
949
|
+
/* @__PURE__ */ jsx8(
|
|
950
|
+
"svg",
|
|
951
|
+
{
|
|
952
|
+
...ICON_PROPS,
|
|
953
|
+
"data-slot": "course-syllabus-chevron",
|
|
954
|
+
className: cn8(
|
|
955
|
+
"size-5 shrink-0 text-muted-foreground transition-transform duration-200 motion-reduce:transition-none",
|
|
956
|
+
isOpen && "rotate-180"
|
|
957
|
+
),
|
|
958
|
+
children: /* @__PURE__ */ jsx8("path", { d: "m6 9 6 6 6-6" })
|
|
959
|
+
}
|
|
960
|
+
)
|
|
961
|
+
]
|
|
962
|
+
}
|
|
963
|
+
),
|
|
964
|
+
/* @__PURE__ */ jsx8(
|
|
965
|
+
"ol",
|
|
966
|
+
{
|
|
967
|
+
id: panelId,
|
|
968
|
+
hidden: !isOpen,
|
|
969
|
+
"data-slot": "course-syllabus-lessons",
|
|
970
|
+
className: "list-none border-t border-border p-0 py-1",
|
|
971
|
+
children: lessons.map((lesson) => {
|
|
972
|
+
const type = lesson.hasVideo ? "video" : "text";
|
|
973
|
+
return /* @__PURE__ */ jsxs6(
|
|
974
|
+
"li",
|
|
975
|
+
{
|
|
976
|
+
"data-slot": "course-syllabus-lesson",
|
|
977
|
+
className: "flex flex-wrap items-center gap-x-3 gap-y-1.5 px-4 py-2.5 text-sm sm:pl-[4.25rem] sm:pr-5",
|
|
978
|
+
children: [
|
|
979
|
+
/* @__PURE__ */ jsx8(LessonIcon, { type }),
|
|
980
|
+
/* @__PURE__ */ jsx8("span", { className: "sr-only", children: type === "video" ? labels.videoLesson : labels.textLesson }),
|
|
981
|
+
/* @__PURE__ */ jsx8(
|
|
982
|
+
"span",
|
|
983
|
+
{
|
|
984
|
+
"data-slot": "course-syllabus-lesson-title",
|
|
985
|
+
className: "min-w-0 flex-1 leading-snug",
|
|
986
|
+
children: lesson.title
|
|
987
|
+
}
|
|
988
|
+
),
|
|
989
|
+
lesson.isFreePreview && renderPreview(lesson),
|
|
990
|
+
hasDuration(lesson) && /* @__PURE__ */ jsx8(
|
|
991
|
+
"time",
|
|
992
|
+
{
|
|
993
|
+
"data-slot": "course-syllabus-lesson-duration",
|
|
994
|
+
dateTime: isoDuration(lesson.durationSeconds),
|
|
995
|
+
className: "ml-auto shrink-0 text-muted-foreground tabular-nums",
|
|
996
|
+
children: formatClock(lesson.durationSeconds)
|
|
997
|
+
}
|
|
998
|
+
)
|
|
999
|
+
]
|
|
1000
|
+
},
|
|
1001
|
+
lesson.id
|
|
1002
|
+
);
|
|
1003
|
+
})
|
|
1004
|
+
}
|
|
1005
|
+
)
|
|
1006
|
+
] }, module.id);
|
|
1007
|
+
}) })
|
|
1008
|
+
]
|
|
1009
|
+
}
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// src/components/data-request-form.tsx
|
|
1014
|
+
import { useEffect as useEffect4, useId as useId3, useRef as useRef4, useState as useState7 } from "react";
|
|
1015
|
+
import { cn as cn9 } from "@reblu/site-ui/utils";
|
|
693
1016
|
import {
|
|
694
1017
|
DATA_REQUEST_TYPES
|
|
695
1018
|
} from "@reblu/site-contracts";
|
|
696
|
-
import { Fragment, jsx as
|
|
1019
|
+
import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
697
1020
|
var DATA_REQUEST_FIELD_LIMITS = Object.freeze({ name: 200, email: 320, details: 5e3 });
|
|
698
1021
|
var DEFAULT_DATA_REQUEST_FORM_LABELS = {
|
|
699
1022
|
requestTypeLabel: "Derecho que quieres ejercer",
|
|
@@ -719,7 +1042,7 @@ var DEFAULT_DATA_REQUEST_FORM_LABELS = {
|
|
|
719
1042
|
nameRequired: "Indica tu nombre.",
|
|
720
1043
|
emailInvalid: "Introduce un email v\xE1lido.",
|
|
721
1044
|
consentRequired: "Debes aceptar el tratamiento de tus datos para gestionar la solicitud.",
|
|
722
|
-
success: (deadline) => /* @__PURE__ */
|
|
1045
|
+
success: (deadline) => /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
723
1046
|
"Hemos recibido tu solicitud. Te responderemos como m\xE1ximo el ",
|
|
724
1047
|
deadline,
|
|
725
1048
|
"."
|
|
@@ -768,16 +1091,16 @@ function DataRequestForm({
|
|
|
768
1091
|
...labels,
|
|
769
1092
|
requestTypes: { ...DEFAULT_DATA_REQUEST_FORM_LABELS.requestTypes, ...labels?.requestTypes }
|
|
770
1093
|
};
|
|
771
|
-
const id =
|
|
772
|
-
const [requestType, setRequestType] =
|
|
773
|
-
const [name, setName] =
|
|
774
|
-
const [email, setEmail] =
|
|
775
|
-
const [details, setDetails] =
|
|
776
|
-
const [consent, setConsent] =
|
|
777
|
-
const [errors, setErrors] =
|
|
778
|
-
const [formError, setFormError] =
|
|
779
|
-
const [submitting, setSubmitting] =
|
|
780
|
-
const [ack, setAck] =
|
|
1094
|
+
const id = useId3();
|
|
1095
|
+
const [requestType, setRequestType] = useState7("");
|
|
1096
|
+
const [name, setName] = useState7("");
|
|
1097
|
+
const [email, setEmail] = useState7("");
|
|
1098
|
+
const [details, setDetails] = useState7("");
|
|
1099
|
+
const [consent, setConsent] = useState7(false);
|
|
1100
|
+
const [errors, setErrors] = useState7({});
|
|
1101
|
+
const [formError, setFormError] = useState7(null);
|
|
1102
|
+
const [submitting, setSubmitting] = useState7(false);
|
|
1103
|
+
const [ack, setAck] = useState7(null);
|
|
781
1104
|
const statusRef = useRef4(null);
|
|
782
1105
|
useEffect4(() => {
|
|
783
1106
|
if (ack) statusRef.current?.focus();
|
|
@@ -830,21 +1153,21 @@ function DataRequestForm({
|
|
|
830
1153
|
dateStyle: "long",
|
|
831
1154
|
timeStyle: "short"
|
|
832
1155
|
}).format(new Date(ack.slaDueAt));
|
|
833
|
-
return /* @__PURE__ */
|
|
1156
|
+
return /* @__PURE__ */ jsx9(
|
|
834
1157
|
"div",
|
|
835
1158
|
{
|
|
836
1159
|
ref: statusRef,
|
|
837
1160
|
tabIndex: -1,
|
|
838
1161
|
role: "status",
|
|
839
1162
|
"data-slot": "data-request-form-success",
|
|
840
|
-
className:
|
|
1163
|
+
className: cn9("rounded-lg border border-border p-6 text-sm", className),
|
|
841
1164
|
children: t.success(deadline)
|
|
842
1165
|
}
|
|
843
1166
|
);
|
|
844
1167
|
}
|
|
845
1168
|
const errorId = (field) => errors[field] ? `${id}-${field}-error` : void 0;
|
|
846
|
-
const fieldError = (field) => errors[field] ? /* @__PURE__ */
|
|
847
|
-
return /* @__PURE__ */
|
|
1169
|
+
const fieldError = (field) => errors[field] ? /* @__PURE__ */ jsx9("p", { id: `${id}-${field}-error`, className: "text-xs text-destructive", children: errors[field] }) : null;
|
|
1170
|
+
return /* @__PURE__ */ jsxs7(
|
|
848
1171
|
"form",
|
|
849
1172
|
{
|
|
850
1173
|
noValidate: true,
|
|
@@ -852,11 +1175,11 @@ function DataRequestForm({
|
|
|
852
1175
|
onFocusCapture: onInteract,
|
|
853
1176
|
onPointerDown: onInteract,
|
|
854
1177
|
"data-slot": "data-request-form",
|
|
855
|
-
className:
|
|
1178
|
+
className: cn9("space-y-4", className),
|
|
856
1179
|
children: [
|
|
857
|
-
/* @__PURE__ */
|
|
858
|
-
/* @__PURE__ */
|
|
859
|
-
/* @__PURE__ */
|
|
1180
|
+
/* @__PURE__ */ jsxs7("div", { className: "space-y-1", children: [
|
|
1181
|
+
/* @__PURE__ */ jsx9("label", { htmlFor: `${id}-type`, className: "text-sm font-medium", children: t.requestTypeLabel }),
|
|
1182
|
+
/* @__PURE__ */ jsxs7(
|
|
860
1183
|
"select",
|
|
861
1184
|
{
|
|
862
1185
|
id: `${id}-type`,
|
|
@@ -864,18 +1187,18 @@ function DataRequestForm({
|
|
|
864
1187
|
onChange: (e) => setRequestType(e.target.value),
|
|
865
1188
|
"aria-invalid": errors.requestType ? true : void 0,
|
|
866
1189
|
"aria-describedby": errorId("requestType"),
|
|
867
|
-
className:
|
|
1190
|
+
className: cn9(FIELD_CLASS, errors.requestType ? "border-destructive" : "border-border"),
|
|
868
1191
|
children: [
|
|
869
|
-
/* @__PURE__ */
|
|
870
|
-
requestTypes.map((type) => /* @__PURE__ */
|
|
1192
|
+
/* @__PURE__ */ jsx9("option", { value: "", children: t.requestTypePlaceholder }),
|
|
1193
|
+
requestTypes.map((type) => /* @__PURE__ */ jsx9("option", { value: type, children: t.requestTypes[type] }, type))
|
|
871
1194
|
]
|
|
872
1195
|
}
|
|
873
1196
|
),
|
|
874
1197
|
fieldError("requestType")
|
|
875
1198
|
] }),
|
|
876
|
-
/* @__PURE__ */
|
|
877
|
-
/* @__PURE__ */
|
|
878
|
-
/* @__PURE__ */
|
|
1199
|
+
/* @__PURE__ */ jsxs7("div", { className: "space-y-1", children: [
|
|
1200
|
+
/* @__PURE__ */ jsx9("label", { htmlFor: `${id}-name`, className: "text-sm font-medium", children: t.nameLabel }),
|
|
1201
|
+
/* @__PURE__ */ jsx9(
|
|
879
1202
|
"input",
|
|
880
1203
|
{
|
|
881
1204
|
id: `${id}-name`,
|
|
@@ -886,14 +1209,14 @@ function DataRequestForm({
|
|
|
886
1209
|
onChange: (e) => setName(e.target.value),
|
|
887
1210
|
"aria-invalid": errors.name ? true : void 0,
|
|
888
1211
|
"aria-describedby": errorId("name"),
|
|
889
|
-
className:
|
|
1212
|
+
className: cn9(FIELD_CLASS, errors.name ? "border-destructive" : "border-border")
|
|
890
1213
|
}
|
|
891
1214
|
),
|
|
892
1215
|
fieldError("name")
|
|
893
1216
|
] }),
|
|
894
|
-
/* @__PURE__ */
|
|
895
|
-
/* @__PURE__ */
|
|
896
|
-
/* @__PURE__ */
|
|
1217
|
+
/* @__PURE__ */ jsxs7("div", { className: "space-y-1", children: [
|
|
1218
|
+
/* @__PURE__ */ jsx9("label", { htmlFor: `${id}-email`, className: "text-sm font-medium", children: t.emailLabel }),
|
|
1219
|
+
/* @__PURE__ */ jsx9(
|
|
897
1220
|
"input",
|
|
898
1221
|
{
|
|
899
1222
|
id: `${id}-email`,
|
|
@@ -904,14 +1227,14 @@ function DataRequestForm({
|
|
|
904
1227
|
onChange: (e) => setEmail(e.target.value),
|
|
905
1228
|
"aria-invalid": errors.email ? true : void 0,
|
|
906
1229
|
"aria-describedby": errorId("email"),
|
|
907
|
-
className:
|
|
1230
|
+
className: cn9(FIELD_CLASS, errors.email ? "border-destructive" : "border-border")
|
|
908
1231
|
}
|
|
909
1232
|
),
|
|
910
1233
|
fieldError("email")
|
|
911
1234
|
] }),
|
|
912
|
-
/* @__PURE__ */
|
|
913
|
-
/* @__PURE__ */
|
|
914
|
-
/* @__PURE__ */
|
|
1235
|
+
/* @__PURE__ */ jsxs7("div", { className: "space-y-1", children: [
|
|
1236
|
+
/* @__PURE__ */ jsx9("label", { htmlFor: `${id}-details`, className: "text-sm font-medium", children: t.detailsLabel }),
|
|
1237
|
+
/* @__PURE__ */ jsx9(
|
|
915
1238
|
"textarea",
|
|
916
1239
|
{
|
|
917
1240
|
id: `${id}-details`,
|
|
@@ -920,13 +1243,13 @@ function DataRequestForm({
|
|
|
920
1243
|
value: details,
|
|
921
1244
|
onChange: (e) => setDetails(e.target.value),
|
|
922
1245
|
placeholder: t.detailsPlaceholder,
|
|
923
|
-
className:
|
|
1246
|
+
className: cn9(FIELD_CLASS, "border-border")
|
|
924
1247
|
}
|
|
925
1248
|
)
|
|
926
1249
|
] }),
|
|
927
|
-
/* @__PURE__ */
|
|
928
|
-
/* @__PURE__ */
|
|
929
|
-
/* @__PURE__ */
|
|
1250
|
+
/* @__PURE__ */ jsxs7("div", { className: "space-y-1", children: [
|
|
1251
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-start gap-2", children: [
|
|
1252
|
+
/* @__PURE__ */ jsx9(
|
|
930
1253
|
"input",
|
|
931
1254
|
{
|
|
932
1255
|
id: `${id}-consent`,
|
|
@@ -938,12 +1261,12 @@ function DataRequestForm({
|
|
|
938
1261
|
className: "mt-1"
|
|
939
1262
|
}
|
|
940
1263
|
),
|
|
941
|
-
/* @__PURE__ */
|
|
1264
|
+
/* @__PURE__ */ jsx9("label", { htmlFor: `${id}-consent`, className: "text-sm text-foreground/80", children: t.consent })
|
|
942
1265
|
] }),
|
|
943
1266
|
fieldError("consent")
|
|
944
1267
|
] }),
|
|
945
|
-
formError && /* @__PURE__ */
|
|
946
|
-
/* @__PURE__ */
|
|
1268
|
+
formError && /* @__PURE__ */ jsx9("p", { role: "alert", className: "text-sm text-destructive", children: formError }),
|
|
1269
|
+
/* @__PURE__ */ jsx9(
|
|
947
1270
|
"button",
|
|
948
1271
|
{
|
|
949
1272
|
type: "submit",
|
|
@@ -960,8 +1283,10 @@ export {
|
|
|
960
1283
|
AddToCartButton,
|
|
961
1284
|
CartIcon,
|
|
962
1285
|
Collection,
|
|
1286
|
+
CourseSyllabus,
|
|
963
1287
|
DATA_REQUEST_FIELD_LIMITS,
|
|
964
1288
|
DATA_REQUEST_TYPES,
|
|
1289
|
+
DEFAULT_COURSE_SYLLABUS_LABELS,
|
|
965
1290
|
DEFAULT_DATA_REQUEST_FORM_LABELS,
|
|
966
1291
|
DEFAULT_PRODUCT_CAROUSEL_LABELS,
|
|
967
1292
|
DataRequestForm,
|