@reblu/site-blocks 0.17.1 → 0.19.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.cjs CHANGED
@@ -37,7 +37,9 @@ __export(client_exports, {
37
37
  DATA_REQUEST_FIELD_LIMITS: () => DATA_REQUEST_FIELD_LIMITS,
38
38
  DATA_REQUEST_TYPES: () => import_site_contracts.DATA_REQUEST_TYPES,
39
39
  DEFAULT_DATA_REQUEST_FORM_LABELS: () => DEFAULT_DATA_REQUEST_FORM_LABELS,
40
+ DEFAULT_PRODUCT_CAROUSEL_LABELS: () => DEFAULT_PRODUCT_CAROUSEL_LABELS,
40
41
  DataRequestForm: () => DataRequestForm,
42
+ ProductCarousel: () => ProductCarousel,
41
43
  ShareButtons: () => ShareButtons,
42
44
  useCollection: () => useCollection
43
45
  });
@@ -226,7 +228,7 @@ function Collection({
226
228
  ] })
227
229
  ] });
228
230
  }
229
- var PAGE_BTN_CLASS = "inline-flex h-9 items-center rounded-md border border-border px-4 text-sm font-medium transition-colors hover:bg-muted disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
231
+ var PAGE_BTN_CLASS = "inline-flex h-9 cursor-pointer items-center rounded-md border border-border px-4 text-sm font-medium transition-colors hover:bg-muted disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
230
232
  function FacetChip({ label, pressed, color, onClick }) {
231
233
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
232
234
  "button",
@@ -236,7 +238,7 @@ function FacetChip({ label, pressed, color, onClick }) {
236
238
  onClick,
237
239
  style: color ? { "--facet-color": color } : void 0,
238
240
  className: (0, import_utils.cn)(
239
- "inline-flex items-center rounded-full border px-3 py-1 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
241
+ "inline-flex cursor-pointer items-center rounded-full border px-3 py-1 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
240
242
  pressed ? color ? "border-transparent bg-[var(--facet-color)] text-white" : "border-transparent bg-primary text-primary-foreground" : "border-border text-muted-foreground hover:bg-muted hover:text-foreground"
241
243
  ),
242
244
  children: label
@@ -321,7 +323,7 @@ function ShareButtons({ url, title, className, labels }) {
321
323
  {
322
324
  type: "button",
323
325
  onClick: handleCopy,
324
- className: (0, import_utils2.cn)(LINK_CLASS, "w-auto gap-2 px-3", copied && "border-primary text-primary"),
326
+ className: (0, import_utils2.cn)(LINK_CLASS, "w-auto cursor-pointer gap-2 px-3", copied && "border-primary text-primary"),
325
327
  "aria-label": t.copy,
326
328
  children: [
327
329
  copied ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CheckIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CopyIcon, {}),
@@ -442,7 +444,7 @@ function AddToCartButton({
442
444
  "data-slot": "add-to-cart-button",
443
445
  "data-status": status,
444
446
  className: (0, import_utils3.cn)(
445
- "inline-flex items-center justify-center gap-2 rounded-xl px-4 py-2.5 text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
447
+ "inline-flex cursor-pointer items-center justify-center gap-2 rounded-xl px-4 py-2.5 text-sm font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
446
448
  isDisabled ? "cursor-not-allowed bg-muted text-muted-foreground" : "bg-primary text-primary-foreground hover:bg-primary/90",
447
449
  status === "error" && "bg-destructive text-destructive-foreground",
448
450
  className
@@ -527,11 +529,214 @@ function CartIcon({ itemCount, href, hydrated, bumpOnAdd, labels, className }) {
527
529
  );
528
530
  }
529
531
 
530
- // src/components/data-request-form.tsx
532
+ // src/components/product-carousel.tsx
531
533
  var import_react4 = require("react");
534
+ var import_button_variants = require("@reblu/site-ui/button-variants");
535
+ var import_utils6 = require("@reblu/site-ui/utils");
536
+
537
+ // src/components/_heading.tsx
538
+ var import_jsx_runtime5 = require("react/jsx-runtime");
539
+ function Heading({
540
+ level,
541
+ className,
542
+ children
543
+ }) {
544
+ const Tag = `h${level}`;
545
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Tag, { className, children });
546
+ }
547
+
548
+ // src/components/product-card.tsx
532
549
  var import_utils5 = require("@reblu/site-ui/utils");
550
+
551
+ // src/lib/format-price.ts
552
+ function formatPrice(cents, currency = "EUR", locale = "es-ES") {
553
+ return new Intl.NumberFormat(locale, {
554
+ style: "currency",
555
+ currency
556
+ }).format(cents / 100);
557
+ }
558
+
559
+ // src/components/product-card.tsx
560
+ var import_jsx_runtime6 = require("react/jsx-runtime");
561
+ function ProductCard({ product, href, currencyCode = "EUR", locale, className }) {
562
+ const image = product.images.find((i) => i.isPrimary) ?? product.images[0];
563
+ const hasDiscount = product.comparePrice != null && product.comparePrice > product.price;
564
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
565
+ "article",
566
+ {
567
+ className: (0, import_utils5.cn)(
568
+ "group flex flex-col overflow-hidden rounded-lg border bg-card transition-shadow hover:shadow-lg",
569
+ className
570
+ ),
571
+ "data-slot": "product-card",
572
+ children: [
573
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "relative aspect-square overflow-hidden bg-muted", children: image ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
574
+ "img",
575
+ {
576
+ src: image.url,
577
+ alt: image.alt ?? product.name,
578
+ loading: "lazy",
579
+ className: "h-full w-full object-cover transition-transform group-hover:scale-105"
580
+ }
581
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex h-full items-center justify-center text-4xl text-muted-foreground/30", "aria-hidden": "true", children: "\u{1F6CD}\uFE0F" }) }),
582
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-1 flex-col gap-2 p-4", children: [
583
+ product.category && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "inline-flex w-fit items-center rounded-full bg-secondary px-2.5 py-0.5 text-xs font-medium text-secondary-foreground", children: product.category.name }),
584
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "text-base font-semibold leading-tight", children: href ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { href, className: "transition-colors hover:text-primary", children: product.name }) : product.name }),
585
+ product.shortDescription && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "line-clamp-2 text-sm text-muted-foreground", children: product.shortDescription }),
586
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mt-auto flex items-baseline gap-2 pt-2", children: [
587
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-lg font-bold text-foreground", children: formatPrice(product.price, currencyCode, locale) }),
588
+ hasDiscount && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-sm text-muted-foreground line-through", children: formatPrice(product.comparePrice, currencyCode, locale) })
589
+ ] })
590
+ ] })
591
+ ]
592
+ }
593
+ );
594
+ }
595
+
596
+ // src/components/product-carousel.tsx
597
+ var import_jsx_runtime7 = require("react/jsx-runtime");
598
+ var DEFAULT_PRODUCT_CAROUSEL_LABELS = {
599
+ region: "Productos relacionados",
600
+ roleDescription: "carrusel",
601
+ previous: "Anterior",
602
+ next: "Siguiente"
603
+ };
604
+ var EDGE_EPSILON = 1;
605
+ function prefersReducedMotion() {
606
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false;
607
+ }
608
+ function ChevronIcon({ direction }) {
609
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
610
+ "svg",
611
+ {
612
+ "aria-hidden": "true",
613
+ viewBox: "0 0 24 24",
614
+ fill: "none",
615
+ stroke: "currentColor",
616
+ strokeWidth: 2,
617
+ strokeLinecap: "round",
618
+ strokeLinejoin: "round",
619
+ className: "rtl:-scale-x-100",
620
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: direction === "left" ? "m15 18-6-6 6-6" : "m9 18 6-6-6-6" })
621
+ }
622
+ );
623
+ }
624
+ function ProductCarousel(props) {
625
+ const { products, heading, headingLevel = 2, headingClassName, labels, className, itemClassName } = props;
626
+ const t = { ...DEFAULT_PRODUCT_CAROUSEL_LABELS, ...labels };
627
+ const trackId = (0, import_react4.useId)();
628
+ const headingId = (0, import_react4.useId)();
629
+ const trackRef = (0, import_react4.useRef)(null);
630
+ const [canPrev, setCanPrev] = (0, import_react4.useState)(false);
631
+ const [canNext, setCanNext] = (0, import_react4.useState)(false);
632
+ const update = (0, import_react4.useCallback)(() => {
633
+ const el = trackRef.current;
634
+ if (!el) return;
635
+ const max = el.scrollWidth - el.clientWidth;
636
+ const pos = Math.abs(el.scrollLeft);
637
+ setCanPrev(pos > EDGE_EPSILON);
638
+ setCanNext(pos < max - EDGE_EPSILON);
639
+ }, []);
640
+ (0, import_react4.useEffect)(() => {
641
+ const el = trackRef.current;
642
+ if (!el) return;
643
+ update();
644
+ el.addEventListener("scroll", update, { passive: true });
645
+ let observer;
646
+ if (typeof ResizeObserver !== "undefined") {
647
+ observer = new ResizeObserver(update);
648
+ observer.observe(el);
649
+ } else {
650
+ window.addEventListener("resize", update);
651
+ }
652
+ return () => {
653
+ el.removeEventListener("scroll", update);
654
+ if (observer) observer.disconnect();
655
+ else window.removeEventListener("resize", update);
656
+ };
657
+ }, [update, products.length]);
658
+ const scrollByPage = (direction) => {
659
+ const el = trackRef.current;
660
+ if (!el) return;
661
+ const rtl = typeof getComputedStyle === "function" && getComputedStyle(el).direction === "rtl";
662
+ el.scrollBy({
663
+ left: direction * el.clientWidth * (rtl ? -1 : 1),
664
+ behavior: prefersReducedMotion() ? "auto" : "smooth"
665
+ });
666
+ };
667
+ if (products.length === 0) return null;
668
+ const scrollable = canPrev || canNext;
669
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { "data-slot": "product-carousel", className: (0, import_utils6.cn)("flex flex-col gap-4", className), children: [
670
+ (heading || scrollable) && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between gap-4", children: [
671
+ heading ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { id: headingId, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Heading, { level: headingLevel, className: headingClassName ?? "text-xl font-semibold", children: heading }) }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", {}),
672
+ scrollable && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hidden gap-2 sm:flex", "data-slot": "product-carousel-controls", children: [
673
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
674
+ "button",
675
+ {
676
+ type: "button",
677
+ className: (0, import_utils6.cn)((0, import_button_variants.buttonVariants)({ variant: "outline", size: "icon" }), "rounded-full"),
678
+ "aria-label": t.previous,
679
+ "aria-controls": trackId,
680
+ disabled: !canPrev,
681
+ onClick: () => scrollByPage(-1),
682
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronIcon, { direction: "left" })
683
+ }
684
+ ),
685
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
686
+ "button",
687
+ {
688
+ type: "button",
689
+ className: (0, import_utils6.cn)((0, import_button_variants.buttonVariants)({ variant: "outline", size: "icon" }), "rounded-full"),
690
+ "aria-label": t.next,
691
+ "aria-controls": trackId,
692
+ disabled: !canNext,
693
+ onClick: () => scrollByPage(1),
694
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChevronIcon, { direction: "right" })
695
+ }
696
+ )
697
+ ] })
698
+ ] }),
699
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
700
+ "div",
701
+ {
702
+ ref: trackRef,
703
+ id: trackId,
704
+ role: "region",
705
+ "aria-roledescription": t.roleDescription,
706
+ ...heading ? { "aria-labelledby": headingId } : { "aria-label": t.region },
707
+ tabIndex: 0,
708
+ "data-slot": "product-carousel-track",
709
+ className: "snap-x snap-mandatory overflow-x-auto overscroll-x-contain scroll-smooth rounded-lg pb-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:scroll-auto",
710
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { role: "list", className: "m-0 flex list-none gap-4 p-0", children: products.map((product, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
711
+ "li",
712
+ {
713
+ className: (0, import_utils6.cn)(
714
+ "shrink-0 snap-start basis-[calc((100%-1rem)/1.5)] sm:basis-[calc((100%-2rem)/3)] lg:basis-[calc((100%-3rem)/4)]",
715
+ itemClassName
716
+ ),
717
+ children: props.renderProduct ? props.renderProduct(product, index) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
718
+ ProductCard,
719
+ {
720
+ product,
721
+ href: props.productHref?.(product),
722
+ currencyCode: props.currencyCode,
723
+ locale: props.locale,
724
+ className: "h-full"
725
+ }
726
+ )
727
+ },
728
+ product.id
729
+ )) })
730
+ }
731
+ )
732
+ ] });
733
+ }
734
+
735
+ // src/components/data-request-form.tsx
736
+ var import_react5 = require("react");
737
+ var import_utils7 = require("@reblu/site-ui/utils");
533
738
  var import_site_contracts = require("@reblu/site-contracts");
534
- var import_jsx_runtime5 = require("react/jsx-runtime");
739
+ var import_jsx_runtime8 = require("react/jsx-runtime");
535
740
  var DATA_REQUEST_FIELD_LIMITS = Object.freeze({ name: 200, email: 320, details: 5e3 });
536
741
  var DEFAULT_DATA_REQUEST_FORM_LABELS = {
537
742
  requestTypeLabel: "Derecho que quieres ejercer",
@@ -557,7 +762,7 @@ var DEFAULT_DATA_REQUEST_FORM_LABELS = {
557
762
  nameRequired: "Indica tu nombre.",
558
763
  emailInvalid: "Introduce un email v\xE1lido.",
559
764
  consentRequired: "Debes aceptar el tratamiento de tus datos para gestionar la solicitud.",
560
- success: (deadline) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
765
+ success: (deadline) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
561
766
  "Hemos recibido tu solicitud. Te responderemos como m\xE1ximo el ",
562
767
  deadline,
563
768
  "."
@@ -606,18 +811,18 @@ function DataRequestForm({
606
811
  ...labels,
607
812
  requestTypes: { ...DEFAULT_DATA_REQUEST_FORM_LABELS.requestTypes, ...labels?.requestTypes }
608
813
  };
609
- const id = (0, import_react4.useId)();
610
- const [requestType, setRequestType] = (0, import_react4.useState)("");
611
- const [name, setName] = (0, import_react4.useState)("");
612
- const [email, setEmail] = (0, import_react4.useState)("");
613
- const [details, setDetails] = (0, import_react4.useState)("");
614
- const [consent, setConsent] = (0, import_react4.useState)(false);
615
- const [errors, setErrors] = (0, import_react4.useState)({});
616
- const [formError, setFormError] = (0, import_react4.useState)(null);
617
- const [submitting, setSubmitting] = (0, import_react4.useState)(false);
618
- const [ack, setAck] = (0, import_react4.useState)(null);
619
- const statusRef = (0, import_react4.useRef)(null);
620
- (0, import_react4.useEffect)(() => {
814
+ const id = (0, import_react5.useId)();
815
+ const [requestType, setRequestType] = (0, import_react5.useState)("");
816
+ const [name, setName] = (0, import_react5.useState)("");
817
+ const [email, setEmail] = (0, import_react5.useState)("");
818
+ const [details, setDetails] = (0, import_react5.useState)("");
819
+ const [consent, setConsent] = (0, import_react5.useState)(false);
820
+ const [errors, setErrors] = (0, import_react5.useState)({});
821
+ const [formError, setFormError] = (0, import_react5.useState)(null);
822
+ const [submitting, setSubmitting] = (0, import_react5.useState)(false);
823
+ const [ack, setAck] = (0, import_react5.useState)(null);
824
+ const statusRef = (0, import_react5.useRef)(null);
825
+ (0, import_react5.useEffect)(() => {
621
826
  if (ack) statusRef.current?.focus();
622
827
  }, [ack]);
623
828
  async function handleSubmit(event) {
@@ -668,21 +873,21 @@ function DataRequestForm({
668
873
  dateStyle: "long",
669
874
  timeStyle: "short"
670
875
  }).format(new Date(ack.slaDueAt));
671
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
876
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
672
877
  "div",
673
878
  {
674
879
  ref: statusRef,
675
880
  tabIndex: -1,
676
881
  role: "status",
677
882
  "data-slot": "data-request-form-success",
678
- className: (0, import_utils5.cn)("rounded-lg border border-border p-6 text-sm", className),
883
+ className: (0, import_utils7.cn)("rounded-lg border border-border p-6 text-sm", className),
679
884
  children: t.success(deadline)
680
885
  }
681
886
  );
682
887
  }
683
888
  const errorId = (field) => errors[field] ? `${id}-${field}-error` : void 0;
684
- const fieldError = (field) => errors[field] ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { id: `${id}-${field}-error`, className: "text-xs text-destructive", children: errors[field] }) : null;
685
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
889
+ const fieldError = (field) => errors[field] ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { id: `${id}-${field}-error`, className: "text-xs text-destructive", children: errors[field] }) : null;
890
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
686
891
  "form",
687
892
  {
688
893
  noValidate: true,
@@ -690,11 +895,11 @@ function DataRequestForm({
690
895
  onFocusCapture: onInteract,
691
896
  onPointerDown: onInteract,
692
897
  "data-slot": "data-request-form",
693
- className: (0, import_utils5.cn)("space-y-4", className),
898
+ className: (0, import_utils7.cn)("space-y-4", className),
694
899
  children: [
695
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-1", children: [
696
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { htmlFor: `${id}-type`, className: "text-sm font-medium", children: t.requestTypeLabel }),
697
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
900
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "space-y-1", children: [
901
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: `${id}-type`, className: "text-sm font-medium", children: t.requestTypeLabel }),
902
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
698
903
  "select",
699
904
  {
700
905
  id: `${id}-type`,
@@ -702,18 +907,18 @@ function DataRequestForm({
702
907
  onChange: (e) => setRequestType(e.target.value),
703
908
  "aria-invalid": errors.requestType ? true : void 0,
704
909
  "aria-describedby": errorId("requestType"),
705
- className: (0, import_utils5.cn)(FIELD_CLASS, errors.requestType ? "border-destructive" : "border-border"),
910
+ className: (0, import_utils7.cn)(FIELD_CLASS, errors.requestType ? "border-destructive" : "border-border"),
706
911
  children: [
707
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "", children: t.requestTypePlaceholder }),
708
- requestTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: type, children: t.requestTypes[type] }, type))
912
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: "", children: t.requestTypePlaceholder }),
913
+ requestTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("option", { value: type, children: t.requestTypes[type] }, type))
709
914
  ]
710
915
  }
711
916
  ),
712
917
  fieldError("requestType")
713
918
  ] }),
714
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-1", children: [
715
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { htmlFor: `${id}-name`, className: "text-sm font-medium", children: t.nameLabel }),
716
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
919
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "space-y-1", children: [
920
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: `${id}-name`, className: "text-sm font-medium", children: t.nameLabel }),
921
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
717
922
  "input",
718
923
  {
719
924
  id: `${id}-name`,
@@ -724,14 +929,14 @@ function DataRequestForm({
724
929
  onChange: (e) => setName(e.target.value),
725
930
  "aria-invalid": errors.name ? true : void 0,
726
931
  "aria-describedby": errorId("name"),
727
- className: (0, import_utils5.cn)(FIELD_CLASS, errors.name ? "border-destructive" : "border-border")
932
+ className: (0, import_utils7.cn)(FIELD_CLASS, errors.name ? "border-destructive" : "border-border")
728
933
  }
729
934
  ),
730
935
  fieldError("name")
731
936
  ] }),
732
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-1", children: [
733
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { htmlFor: `${id}-email`, className: "text-sm font-medium", children: t.emailLabel }),
734
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
937
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "space-y-1", children: [
938
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: `${id}-email`, className: "text-sm font-medium", children: t.emailLabel }),
939
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
735
940
  "input",
736
941
  {
737
942
  id: `${id}-email`,
@@ -742,14 +947,14 @@ function DataRequestForm({
742
947
  onChange: (e) => setEmail(e.target.value),
743
948
  "aria-invalid": errors.email ? true : void 0,
744
949
  "aria-describedby": errorId("email"),
745
- className: (0, import_utils5.cn)(FIELD_CLASS, errors.email ? "border-destructive" : "border-border")
950
+ className: (0, import_utils7.cn)(FIELD_CLASS, errors.email ? "border-destructive" : "border-border")
746
951
  }
747
952
  ),
748
953
  fieldError("email")
749
954
  ] }),
750
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-1", children: [
751
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { htmlFor: `${id}-details`, className: "text-sm font-medium", children: t.detailsLabel }),
752
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
955
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "space-y-1", children: [
956
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: `${id}-details`, className: "text-sm font-medium", children: t.detailsLabel }),
957
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
753
958
  "textarea",
754
959
  {
755
960
  id: `${id}-details`,
@@ -758,13 +963,13 @@ function DataRequestForm({
758
963
  value: details,
759
964
  onChange: (e) => setDetails(e.target.value),
760
965
  placeholder: t.detailsPlaceholder,
761
- className: (0, import_utils5.cn)(FIELD_CLASS, "border-border")
966
+ className: (0, import_utils7.cn)(FIELD_CLASS, "border-border")
762
967
  }
763
968
  )
764
969
  ] }),
765
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-1", children: [
766
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-start gap-2", children: [
767
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
970
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "space-y-1", children: [
971
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-start gap-2", children: [
972
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
768
973
  "input",
769
974
  {
770
975
  id: `${id}-consent`,
@@ -776,17 +981,17 @@ function DataRequestForm({
776
981
  className: "mt-1"
777
982
  }
778
983
  ),
779
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("label", { htmlFor: `${id}-consent`, className: "text-sm text-foreground/80", children: t.consent })
984
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("label", { htmlFor: `${id}-consent`, className: "text-sm text-foreground/80", children: t.consent })
780
985
  ] }),
781
986
  fieldError("consent")
782
987
  ] }),
783
- formError && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { role: "alert", className: "text-sm text-destructive", children: formError }),
784
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
988
+ formError && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { role: "alert", className: "text-sm text-destructive", children: formError }),
989
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
785
990
  "button",
786
991
  {
787
992
  type: "submit",
788
993
  disabled: submitting,
789
- className: "rounded-lg bg-primary px-6 py-2 text-sm font-semibold text-primary-foreground disabled:opacity-60",
994
+ className: "cursor-pointer rounded-lg bg-primary px-6 py-2 text-sm font-semibold text-primary-foreground disabled:cursor-not-allowed disabled:opacity-60",
790
995
  children: submitting ? t.submitting : t.submit
791
996
  }
792
997
  )
@@ -802,7 +1007,9 @@ function DataRequestForm({
802
1007
  DATA_REQUEST_FIELD_LIMITS,
803
1008
  DATA_REQUEST_TYPES,
804
1009
  DEFAULT_DATA_REQUEST_FORM_LABELS,
1010
+ DEFAULT_PRODUCT_CAROUSEL_LABELS,
805
1011
  DataRequestForm,
1012
+ ProductCarousel,
806
1013
  ShareButtons,
807
1014
  useCollection
808
1015
  });