@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/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: (count) => `${count} art\xEDculo${count === 1 ? "" : "s"}`
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/data-request-form.tsx
691
- import { useEffect as useEffect4, useId as useId2, useRef as useRef4, useState as useState6 } from "react";
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 jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
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__ */ jsxs6(Fragment, { children: [
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 = useId2();
772
- const [requestType, setRequestType] = useState6("");
773
- const [name, setName] = useState6("");
774
- const [email, setEmail] = useState6("");
775
- const [details, setDetails] = useState6("");
776
- const [consent, setConsent] = useState6(false);
777
- const [errors, setErrors] = useState6({});
778
- const [formError, setFormError] = useState6(null);
779
- const [submitting, setSubmitting] = useState6(false);
780
- const [ack, setAck] = useState6(null);
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__ */ jsx8(
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: cn7("rounded-lg border border-border p-6 text-sm", 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__ */ jsx8("p", { id: `${id}-${field}-error`, className: "text-xs text-destructive", children: errors[field] }) : null;
847
- return /* @__PURE__ */ jsxs6(
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: cn7("space-y-4", className),
1178
+ className: cn9("space-y-4", className),
856
1179
  children: [
857
- /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
858
- /* @__PURE__ */ jsx8("label", { htmlFor: `${id}-type`, className: "text-sm font-medium", children: t.requestTypeLabel }),
859
- /* @__PURE__ */ jsxs6(
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: cn7(FIELD_CLASS, errors.requestType ? "border-destructive" : "border-border"),
1190
+ className: cn9(FIELD_CLASS, errors.requestType ? "border-destructive" : "border-border"),
868
1191
  children: [
869
- /* @__PURE__ */ jsx8("option", { value: "", children: t.requestTypePlaceholder }),
870
- requestTypes.map((type) => /* @__PURE__ */ jsx8("option", { value: type, children: t.requestTypes[type] }, type))
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__ */ jsxs6("div", { className: "space-y-1", children: [
877
- /* @__PURE__ */ jsx8("label", { htmlFor: `${id}-name`, className: "text-sm font-medium", children: t.nameLabel }),
878
- /* @__PURE__ */ jsx8(
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: cn7(FIELD_CLASS, errors.name ? "border-destructive" : "border-border")
1212
+ className: cn9(FIELD_CLASS, errors.name ? "border-destructive" : "border-border")
890
1213
  }
891
1214
  ),
892
1215
  fieldError("name")
893
1216
  ] }),
894
- /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
895
- /* @__PURE__ */ jsx8("label", { htmlFor: `${id}-email`, className: "text-sm font-medium", children: t.emailLabel }),
896
- /* @__PURE__ */ jsx8(
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: cn7(FIELD_CLASS, errors.email ? "border-destructive" : "border-border")
1230
+ className: cn9(FIELD_CLASS, errors.email ? "border-destructive" : "border-border")
908
1231
  }
909
1232
  ),
910
1233
  fieldError("email")
911
1234
  ] }),
912
- /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
913
- /* @__PURE__ */ jsx8("label", { htmlFor: `${id}-details`, className: "text-sm font-medium", children: t.detailsLabel }),
914
- /* @__PURE__ */ jsx8(
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: cn7(FIELD_CLASS, "border-border")
1246
+ className: cn9(FIELD_CLASS, "border-border")
924
1247
  }
925
1248
  )
926
1249
  ] }),
927
- /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
928
- /* @__PURE__ */ jsxs6("div", { className: "flex items-start gap-2", children: [
929
- /* @__PURE__ */ jsx8(
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__ */ jsx8("label", { htmlFor: `${id}-consent`, className: "text-sm text-foreground/80", children: t.consent })
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__ */ jsx8("p", { role: "alert", className: "text-sm text-destructive", children: formError }),
946
- /* @__PURE__ */ jsx8(
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,