@opensite/ui 2.9.0 → 2.9.2

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.
Files changed (51) hide show
  1. package/dist/carousel-feature-badge.cjs +4 -3
  2. package/dist/carousel-feature-badge.d.cts +1 -1
  3. package/dist/carousel-feature-badge.d.ts +1 -1
  4. package/dist/carousel-feature-badge.js +4 -3
  5. package/dist/carousel-scrolling-feature-showcase.cjs +47 -38
  6. package/dist/carousel-scrolling-feature-showcase.js +47 -38
  7. package/dist/registry.cjs +454 -265
  8. package/dist/registry.js +454 -265
  9. package/dist/testimonials-grid-add-review.cjs +578 -39
  10. package/dist/testimonials-grid-add-review.d.cts +26 -26
  11. package/dist/testimonials-grid-add-review.d.ts +26 -26
  12. package/dist/testimonials-grid-add-review.js +577 -38
  13. package/dist/testimonials-images-helpful.cjs +85 -74
  14. package/dist/testimonials-images-helpful.js +85 -74
  15. package/dist/testimonials-list-verified.cjs +1 -0
  16. package/dist/testimonials-list-verified.js +1 -0
  17. package/dist/testimonials-logo-cards.cjs +8 -5
  18. package/dist/testimonials-logo-cards.js +8 -5
  19. package/dist/testimonials-masonry-grid.cjs +87 -11
  20. package/dist/testimonials-masonry-grid.d.cts +14 -1
  21. package/dist/testimonials-masonry-grid.d.ts +14 -1
  22. package/dist/testimonials-masonry-grid.js +88 -12
  23. package/dist/testimonials-mini-dividers.cjs +438 -26
  24. package/dist/testimonials-mini-dividers.js +434 -22
  25. package/dist/testimonials-minimal-numbered.cjs +1 -1
  26. package/dist/testimonials-minimal-numbered.js +1 -1
  27. package/dist/testimonials-parallax-number.cjs +1 -1
  28. package/dist/testimonials-parallax-number.js +1 -1
  29. package/dist/testimonials-quote-carousel.cjs +39 -37
  30. package/dist/testimonials-quote-carousel.d.cts +5 -1
  31. package/dist/testimonials-quote-carousel.d.ts +5 -1
  32. package/dist/testimonials-quote-carousel.js +39 -37
  33. package/dist/testimonials-scrolling-columns.cjs +438 -8
  34. package/dist/testimonials-scrolling-columns.js +436 -6
  35. package/dist/testimonials-simple-grid.cjs +82 -6
  36. package/dist/testimonials-simple-grid.d.cts +14 -1
  37. package/dist/testimonials-simple-grid.d.ts +14 -1
  38. package/dist/testimonials-simple-grid.js +83 -7
  39. package/dist/testimonials-stats-header.cjs +88 -8
  40. package/dist/testimonials-stats-header.d.cts +14 -1
  41. package/dist/testimonials-stats-header.d.ts +14 -1
  42. package/dist/testimonials-stats-header.js +89 -9
  43. package/dist/testimonials-twitter-cards.cjs +150 -25
  44. package/dist/testimonials-twitter-cards.d.cts +14 -1
  45. package/dist/testimonials-twitter-cards.d.ts +14 -1
  46. package/dist/testimonials-twitter-cards.js +151 -26
  47. package/dist/testimonials-wall-compact.cjs +529 -50
  48. package/dist/testimonials-wall-compact.d.cts +14 -1
  49. package/dist/testimonials-wall-compact.d.ts +14 -1
  50. package/dist/testimonials-wall-compact.js +526 -44
  51. package/package.json +1 -1
@@ -1,19 +1,20 @@
1
1
  "use client";
2
- import * as React from 'react';
3
- import React__default, { useCallback, useMemo } from 'react';
2
+ import * as React4 from 'react';
3
+ import React4__default, { useCallback, useMemo } from 'react';
4
4
  import { motion } from 'framer-motion';
5
5
  import { clsx } from 'clsx';
6
6
  import { twMerge } from 'tailwind-merge';
7
7
  import { Icon } from '@page-speed/icon';
8
8
  import { jsx, jsxs } from 'react/jsx-runtime';
9
9
  import { Img } from '@page-speed/img';
10
+ import { cva } from 'class-variance-authority';
10
11
 
11
12
  // components/blocks/testimonials/testimonials-scrolling-columns.tsx
12
13
  function cn(...inputs) {
13
14
  return twMerge(clsx(inputs));
14
15
  }
15
16
  var DEFAULT_ICON_API_KEY = "au382bi7fsh96w9h9xlrnat2jglx";
16
- var DynamicIcon = React.memo(function DynamicIcon2({
17
+ var DynamicIcon = React4.memo(function DynamicIcon2({
17
18
  apiKey,
18
19
  ...props
19
20
  }) {
@@ -90,7 +91,7 @@ var maxWidthStyles = {
90
91
  "4xl": "max-w-[1536px]",
91
92
  full: "max-w-full"
92
93
  };
93
- var Container = React__default.forwardRef(
94
+ var Container = React4__default.forwardRef(
94
95
  ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
95
96
  const Component = as;
96
97
  return /* @__PURE__ */ jsx(
@@ -396,7 +397,7 @@ var spacingStyles = {
396
397
  };
397
398
  var predefinedSpacings = ["none", "sm", "md", "lg", "xl", "hero"];
398
399
  var isPredefinedSpacing = (spacing) => predefinedSpacings.includes(spacing);
399
- var Section = React__default.forwardRef(
400
+ var Section = React4__default.forwardRef(
400
401
  ({
401
402
  id,
402
403
  title,
@@ -457,6 +458,424 @@ var Section = React__default.forwardRef(
457
458
  }
458
459
  );
459
460
  Section.displayName = "Section";
461
+ var baseStyles = [
462
+ // Layout
463
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap shrink-0",
464
+ // Typography - using CSS variables with sensible defaults
465
+ "font-[var(--button-font-family,inherit)]",
466
+ "font-[var(--button-font-weight,500)]",
467
+ "tracking-[var(--button-letter-spacing,0)]",
468
+ "leading-[var(--button-line-height,1.25)]",
469
+ "[text-transform:var(--button-text-transform,none)]",
470
+ "text-sm",
471
+ // Border radius
472
+ "rounded-[var(--button-radius,var(--radius,0.375rem))]",
473
+ // Smooth transition - using [transition:...] to set full shorthand property (not just transition-property)
474
+ "[transition:var(--button-transition,all_250ms_cubic-bezier(0.4,0,0.2,1))]",
475
+ // Box shadow (master level) - using [box-shadow:...] for complex multi-value shadows
476
+ "[box-shadow:var(--button-shadow,none)]",
477
+ "hover:[box-shadow:var(--button-shadow-hover,var(--button-shadow,none))]",
478
+ // Disabled state
479
+ "disabled:pointer-events-none disabled:opacity-50",
480
+ // SVG handling
481
+ "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
482
+ // Focus styles
483
+ "outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
484
+ // Invalid state
485
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
486
+ ].join(" ");
487
+ var buttonVariants = cva(baseStyles, {
488
+ variants: {
489
+ variant: {
490
+ // Default (Primary) variant - full customization
491
+ default: [
492
+ "bg-[var(--button-default-bg,hsl(var(--primary)))]",
493
+ "text-[var(--button-default-fg,hsl(var(--primary-foreground)))]",
494
+ "border-[length:var(--button-default-border-width,0px)]",
495
+ "border-[color:var(--button-default-border,transparent)]",
496
+ "[box-shadow:var(--button-default-shadow,var(--button-shadow,none))]",
497
+ "hover:bg-[var(--button-default-hover-bg,hsl(var(--primary)/0.9))]",
498
+ "hover:text-[var(--button-default-hover-fg,var(--button-default-fg,hsl(var(--primary-foreground))))]",
499
+ "hover:border-[color:var(--button-default-hover-border,var(--button-default-border,transparent))]",
500
+ "hover:[box-shadow:var(--button-default-shadow-hover,var(--button-shadow-hover,var(--button-default-shadow,var(--button-shadow,none))))]"
501
+ ].join(" "),
502
+ // Destructive variant - full customization
503
+ destructive: [
504
+ "bg-[var(--button-destructive-bg,hsl(var(--destructive)))]",
505
+ "text-[var(--button-destructive-fg,white)]",
506
+ "border-[length:var(--button-destructive-border-width,0px)]",
507
+ "border-[color:var(--button-destructive-border,transparent)]",
508
+ "[box-shadow:var(--button-destructive-shadow,var(--button-shadow,none))]",
509
+ "hover:bg-[var(--button-destructive-hover-bg,hsl(var(--destructive)/0.9))]",
510
+ "hover:text-[var(--button-destructive-hover-fg,var(--button-destructive-fg,white))]",
511
+ "hover:border-[color:var(--button-destructive-hover-border,var(--button-destructive-border,transparent))]",
512
+ "hover:[box-shadow:var(--button-destructive-shadow-hover,var(--button-shadow-hover,var(--button-destructive-shadow,var(--button-shadow,none))))]",
513
+ "focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
514
+ "dark:bg-destructive/60"
515
+ ].join(" "),
516
+ // Outline variant - full customization with proper border handling
517
+ outline: [
518
+ "bg-[var(--button-outline-bg,hsl(var(--background)))]",
519
+ "text-[var(--button-outline-fg,inherit)]",
520
+ "border-[length:var(--button-outline-border-width,1px)]",
521
+ "border-[color:var(--button-outline-border,hsl(var(--border)))]",
522
+ "[box-shadow:var(--button-outline-shadow,var(--button-shadow,0_1px_2px_0_rgb(0_0_0/0.05)))]",
523
+ "hover:bg-[var(--button-outline-hover-bg,hsl(var(--accent)))]",
524
+ "hover:text-[var(--button-outline-hover-fg,hsl(var(--accent-foreground)))]",
525
+ "hover:border-[color:var(--button-outline-hover-border,var(--button-outline-border,hsl(var(--border))))]",
526
+ "hover:[box-shadow:var(--button-outline-shadow-hover,var(--button-shadow-hover,var(--button-outline-shadow,var(--button-shadow,none))))]",
527
+ "dark:bg-input/30 dark:border-input dark:hover:bg-input/50"
528
+ ].join(" "),
529
+ // Secondary variant - full customization
530
+ secondary: [
531
+ "bg-[var(--button-secondary-bg,hsl(var(--secondary)))]",
532
+ "text-[var(--button-secondary-fg,hsl(var(--secondary-foreground)))]",
533
+ "border-[length:var(--button-secondary-border-width,0px)]",
534
+ "border-[color:var(--button-secondary-border,transparent)]",
535
+ "[box-shadow:var(--button-secondary-shadow,var(--button-shadow,none))]",
536
+ "hover:bg-[var(--button-secondary-hover-bg,hsl(var(--secondary)/0.8))]",
537
+ "hover:text-[var(--button-secondary-hover-fg,var(--button-secondary-fg,hsl(var(--secondary-foreground))))]",
538
+ "hover:border-[color:var(--button-secondary-hover-border,var(--button-secondary-border,transparent))]",
539
+ "hover:[box-shadow:var(--button-secondary-shadow-hover,var(--button-shadow-hover,var(--button-secondary-shadow,var(--button-shadow,none))))]"
540
+ ].join(" "),
541
+ // Ghost variant - full customization
542
+ ghost: [
543
+ "bg-[var(--button-ghost-bg,transparent)]",
544
+ "text-[var(--button-ghost-fg,inherit)]",
545
+ "border-[length:var(--button-ghost-border-width,0px)]",
546
+ "border-[color:var(--button-ghost-border,transparent)]",
547
+ "[box-shadow:var(--button-ghost-shadow,var(--button-shadow,none))]",
548
+ "hover:bg-[var(--button-ghost-hover-bg,hsl(var(--accent)))]",
549
+ "hover:text-[var(--button-ghost-hover-fg,hsl(var(--accent-foreground)))]",
550
+ "hover:border-[color:var(--button-ghost-hover-border,var(--button-ghost-border,transparent))]",
551
+ "hover:[box-shadow:var(--button-ghost-shadow-hover,var(--button-shadow-hover,var(--button-ghost-shadow,var(--button-shadow,none))))]",
552
+ "dark:hover:bg-accent/50"
553
+ ].join(" "),
554
+ // Link variant - full customization
555
+ link: [
556
+ "bg-[var(--button-link-bg,transparent)]",
557
+ "text-[var(--button-link-fg,hsl(var(--primary)))]",
558
+ "border-[length:var(--button-link-border-width,0px)]",
559
+ "border-[color:var(--button-link-border,transparent)]",
560
+ "[box-shadow:var(--button-link-shadow,none)]",
561
+ "hover:bg-[var(--button-link-hover-bg,transparent)]",
562
+ "hover:text-[var(--button-link-hover-fg,var(--button-link-fg,hsl(var(--primary))))]",
563
+ "hover:[box-shadow:var(--button-link-shadow-hover,none)]",
564
+ "underline-offset-4 hover:underline"
565
+ ].join(" ")
566
+ },
567
+ size: {
568
+ default: [
569
+ "h-[var(--button-height-md,2.25rem)]",
570
+ "px-[var(--button-padding-x-md,1rem)]",
571
+ "py-[var(--button-padding-y-md,0.5rem)]",
572
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
573
+ ].join(" "),
574
+ sm: [
575
+ "h-[var(--button-height-sm,2rem)]",
576
+ "px-[var(--button-padding-x-sm,0.75rem)]",
577
+ "py-[var(--button-padding-y-sm,0.25rem)]",
578
+ "gap-1.5",
579
+ "has-[>svg]:px-[calc(var(--button-padding-x-sm,0.75rem)*0.83)]"
580
+ ].join(" "),
581
+ md: [
582
+ "h-[var(--button-height-md,2.25rem)]",
583
+ "px-[var(--button-padding-x-md,1rem)]",
584
+ "py-[var(--button-padding-y-md,0.5rem)]",
585
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
586
+ ].join(" "),
587
+ lg: [
588
+ "h-[var(--button-height-lg,2.5rem)]",
589
+ "px-[var(--button-padding-x-lg,1.5rem)]",
590
+ "py-[var(--button-padding-y-lg,0.5rem)]",
591
+ "has-[>svg]:px-[calc(var(--button-padding-x-lg,1.5rem)*0.67)]"
592
+ ].join(" "),
593
+ icon: "size-[var(--button-height-md,2.25rem)]",
594
+ "icon-sm": "size-[var(--button-height-sm,2rem)]",
595
+ "icon-lg": "size-[var(--button-height-lg,2.5rem)]"
596
+ }
597
+ },
598
+ defaultVariants: {
599
+ variant: "default",
600
+ size: "default"
601
+ }
602
+ });
603
+ function normalizePhoneNumber(input) {
604
+ const trimmed = input.trim();
605
+ if (trimmed.toLowerCase().startsWith("tel:")) {
606
+ return trimmed;
607
+ }
608
+ const match = trimmed.match(/^[\s\+\-\(\)]*(\d[\d\s\-\(\)\.]*\d)[\s\-]*(x|ext\.?|extension)?[\s\-]*(\d+)?$/i);
609
+ if (match) {
610
+ const mainNumber = match[1].replace(/[\s\-\(\)\.]/g, "");
611
+ const extension = match[3];
612
+ const normalized = mainNumber.length >= 10 && !trimmed.startsWith("+") ? `+${mainNumber}` : mainNumber;
613
+ const withExtension = extension ? `${normalized};ext=${extension}` : normalized;
614
+ return `tel:${withExtension}`;
615
+ }
616
+ const cleaned = trimmed.replace(/[\s\-\(\)\.]/g, "");
617
+ return `tel:${cleaned}`;
618
+ }
619
+ function normalizeEmail(input) {
620
+ const trimmed = input.trim();
621
+ if (trimmed.toLowerCase().startsWith("mailto:")) {
622
+ return trimmed;
623
+ }
624
+ return `mailto:${trimmed}`;
625
+ }
626
+ function isEmail(input) {
627
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
628
+ return emailRegex.test(input.trim());
629
+ }
630
+ function isPhoneNumber(input) {
631
+ const trimmed = input.trim();
632
+ if (trimmed.toLowerCase().startsWith("tel:")) {
633
+ return true;
634
+ }
635
+ const phoneRegex = /^[\s\+\-\(\)]*\d[\d\s\-\(\)\.]*\d[\s\-]*(x|ext\.?|extension)?[\s\-]*\d*$/i;
636
+ return phoneRegex.test(trimmed);
637
+ }
638
+ function isInternalUrl(href) {
639
+ if (typeof window === "undefined") {
640
+ return href.startsWith("/") && !href.startsWith("//");
641
+ }
642
+ const trimmed = href.trim();
643
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
644
+ return true;
645
+ }
646
+ try {
647
+ const url = new URL(trimmed, window.location.href);
648
+ const currentOrigin = window.location.origin;
649
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
650
+ return normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin);
651
+ } catch {
652
+ return false;
653
+ }
654
+ }
655
+ function toRelativePath(href) {
656
+ if (typeof window === "undefined") {
657
+ return href;
658
+ }
659
+ const trimmed = href.trim();
660
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
661
+ return trimmed;
662
+ }
663
+ try {
664
+ const url = new URL(trimmed, window.location.href);
665
+ const currentOrigin = window.location.origin;
666
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
667
+ if (normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin)) {
668
+ return url.pathname + url.search + url.hash;
669
+ }
670
+ } catch {
671
+ }
672
+ return trimmed;
673
+ }
674
+ function useNavigation({
675
+ href,
676
+ onClick
677
+ } = {}) {
678
+ const linkType = React4.useMemo(() => {
679
+ if (!href || href.trim() === "") {
680
+ return onClick ? "none" : "none";
681
+ }
682
+ const trimmed = href.trim();
683
+ if (trimmed.toLowerCase().startsWith("mailto:") || isEmail(trimmed)) {
684
+ return "mailto";
685
+ }
686
+ if (trimmed.toLowerCase().startsWith("tel:") || isPhoneNumber(trimmed)) {
687
+ return "tel";
688
+ }
689
+ if (isInternalUrl(trimmed)) {
690
+ return "internal";
691
+ }
692
+ try {
693
+ new URL(trimmed, typeof window !== "undefined" ? window.location.href : "http://localhost");
694
+ return "external";
695
+ } catch {
696
+ return "internal";
697
+ }
698
+ }, [href, onClick]);
699
+ const normalizedHref = React4.useMemo(() => {
700
+ if (!href || href.trim() === "") {
701
+ return void 0;
702
+ }
703
+ const trimmed = href.trim();
704
+ switch (linkType) {
705
+ case "tel":
706
+ return normalizePhoneNumber(trimmed);
707
+ case "mailto":
708
+ return normalizeEmail(trimmed);
709
+ case "internal":
710
+ return toRelativePath(trimmed);
711
+ case "external":
712
+ return trimmed;
713
+ default:
714
+ return trimmed;
715
+ }
716
+ }, [href, linkType]);
717
+ const target = React4.useMemo(() => {
718
+ switch (linkType) {
719
+ case "external":
720
+ return "_blank";
721
+ case "internal":
722
+ return "_self";
723
+ case "mailto":
724
+ case "tel":
725
+ return void 0;
726
+ default:
727
+ return void 0;
728
+ }
729
+ }, [linkType]);
730
+ const rel = React4.useMemo(() => {
731
+ if (linkType === "external") {
732
+ return "noopener noreferrer";
733
+ }
734
+ return void 0;
735
+ }, [linkType]);
736
+ const isExternal = linkType === "external";
737
+ const isInternal = linkType === "internal";
738
+ const shouldUseRouter = isInternal && typeof normalizedHref === "string" && normalizedHref.startsWith("/");
739
+ const handleClick = React4.useCallback(
740
+ (event) => {
741
+ if (onClick) {
742
+ try {
743
+ onClick(event);
744
+ } catch (error) {
745
+ console.error("Error in user onClick handler:", error);
746
+ }
747
+ }
748
+ if (event.defaultPrevented) {
749
+ return;
750
+ }
751
+ if (shouldUseRouter && normalizedHref && event.button === 0 && // left-click only
752
+ !event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey) {
753
+ if (typeof window !== "undefined") {
754
+ const handler = window.__opensiteNavigationHandler;
755
+ if (typeof handler === "function") {
756
+ try {
757
+ const handled = handler(normalizedHref, event.nativeEvent || event);
758
+ if (handled !== false) {
759
+ event.preventDefault();
760
+ }
761
+ } catch (error) {
762
+ console.error("Error in navigation handler:", error);
763
+ }
764
+ }
765
+ }
766
+ }
767
+ },
768
+ [onClick, shouldUseRouter, normalizedHref]
769
+ );
770
+ return {
771
+ linkType,
772
+ normalizedHref,
773
+ target,
774
+ rel,
775
+ isExternal,
776
+ isInternal,
777
+ shouldUseRouter,
778
+ handleClick
779
+ };
780
+ }
781
+ var Pressable = React4.forwardRef(
782
+ ({
783
+ children,
784
+ className,
785
+ href,
786
+ onClick,
787
+ variant,
788
+ size,
789
+ asButton = false,
790
+ fallbackComponentType = "span",
791
+ componentType,
792
+ "aria-label": ariaLabel,
793
+ "aria-describedby": ariaDescribedby,
794
+ id,
795
+ ...props
796
+ }, ref) => {
797
+ const navigation = useNavigation({ href, onClick });
798
+ const {
799
+ normalizedHref,
800
+ target,
801
+ rel,
802
+ linkType,
803
+ isInternal,
804
+ handleClick
805
+ } = navigation;
806
+ const shouldRenderLink = normalizedHref && linkType !== "none";
807
+ const shouldRenderButton = !shouldRenderLink && onClick;
808
+ const effectiveComponentType = componentType || (shouldRenderLink ? "a" : shouldRenderButton ? "button" : fallbackComponentType);
809
+ const finalComponentType = isInternal && shouldRenderLink ? "a" : effectiveComponentType;
810
+ const shouldApplyButtonStyles = asButton || variant || size;
811
+ const combinedClassName = cn(
812
+ shouldApplyButtonStyles && buttonVariants({ variant, size }),
813
+ className
814
+ );
815
+ const dataProps = Object.fromEntries(
816
+ Object.entries(props).filter(([key]) => key.startsWith("data-"))
817
+ );
818
+ const buttonDataAttributes = shouldApplyButtonStyles ? {
819
+ "data-slot": "button",
820
+ "data-variant": variant ?? "default",
821
+ "data-size": size ?? "default"
822
+ } : {};
823
+ const commonProps = {
824
+ className: combinedClassName,
825
+ onClick: handleClick,
826
+ "aria-label": ariaLabel,
827
+ "aria-describedby": ariaDescribedby,
828
+ id,
829
+ ...dataProps,
830
+ ...buttonDataAttributes
831
+ };
832
+ if (finalComponentType === "a" && shouldRenderLink) {
833
+ return /* @__PURE__ */ jsx(
834
+ "a",
835
+ {
836
+ ref,
837
+ href: normalizedHref,
838
+ target,
839
+ rel,
840
+ ...commonProps,
841
+ ...props,
842
+ children
843
+ }
844
+ );
845
+ }
846
+ if (finalComponentType === "button") {
847
+ return /* @__PURE__ */ jsx(
848
+ "button",
849
+ {
850
+ ref,
851
+ type: props.type || "button",
852
+ ...commonProps,
853
+ ...props,
854
+ children
855
+ }
856
+ );
857
+ }
858
+ if (finalComponentType === "div") {
859
+ return /* @__PURE__ */ jsx(
860
+ "div",
861
+ {
862
+ ref,
863
+ ...commonProps,
864
+ children
865
+ }
866
+ );
867
+ }
868
+ return /* @__PURE__ */ jsx(
869
+ "span",
870
+ {
871
+ ref,
872
+ ...commonProps,
873
+ children
874
+ }
875
+ );
876
+ }
877
+ );
878
+ Pressable.displayName = "Pressable";
460
879
  var containerVariants = {
461
880
  hidden: {},
462
881
  visible: {
@@ -571,7 +990,18 @@ function TestimonialsScrollingColumns({
571
990
  ) : testimonial.quote),
572
991
  /* @__PURE__ */ jsx("figcaption", { className: cn("mt-4", authorClassName), children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
573
992
  testimonial.author && (typeof testimonial.author === "string" ? /* @__PURE__ */ jsx("div", { className: "uppercase text-base font-semibold", children: testimonial.author }) : null),
574
- testimonial.role && (typeof testimonial.role === "string" ? /* @__PURE__ */ jsx("div", { className: "text-sm font-thin", children: testimonial.role }) : null)
993
+ testimonial.role && (typeof testimonial.role === "string" ? /* @__PURE__ */ jsx("div", { className: "text-sm font-thin opacity-75", children: testimonial.role }) : null),
994
+ testimonial.linkConfig?.href ? /* @__PURE__ */ jsx(
995
+ Pressable,
996
+ {
997
+ href: testimonial.linkConfig.href,
998
+ className: cn(
999
+ "text-base font-bold",
1000
+ testimonial.linkConfig.className
1001
+ ),
1002
+ children: testimonial.linkConfig.label || "Full Review"
1003
+ }
1004
+ ) : null
575
1005
  ] }) })
576
1006
  ] })
577
1007
  ]
@@ -882,6 +882,70 @@ var Pressable = React3__namespace.forwardRef(
882
882
  }
883
883
  );
884
884
  Pressable.displayName = "Pressable";
885
+ var MOBILE_CLASSES = {
886
+ "fit-left": "items-start md:items-center",
887
+ "fit-center": "items-center",
888
+ "fit-right": "items-end md:items-center",
889
+ "full-left": "items-stretch md:items-center",
890
+ "full-center": "items-stretch md:items-center",
891
+ "full-right": "items-stretch md:items-center"
892
+ };
893
+ function BlockActions({
894
+ mobileConfig,
895
+ actionsClassName,
896
+ verticalSpacing = "mt-4 md:mt-8",
897
+ actions,
898
+ actionsSlot
899
+ }) {
900
+ const width = mobileConfig?.width ?? "full";
901
+ const position = mobileConfig?.position ?? "center";
902
+ const mobileLayoutClass = MOBILE_CLASSES[`${width}-${position}`];
903
+ if (actionsSlot) {
904
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { children: actionsSlot });
905
+ } else if (actions && actions?.length > 0) {
906
+ return /* @__PURE__ */ jsxRuntime.jsx(
907
+ "div",
908
+ {
909
+ className: cn(
910
+ "flex flex-col md:flex-row flex-wrap gap-4",
911
+ mobileLayoutClass,
912
+ actionsClassName,
913
+ verticalSpacing
914
+ ),
915
+ children: actions.map((action, index) => /* @__PURE__ */ jsxRuntime.jsx(ActionComponent, { action }, index))
916
+ }
917
+ );
918
+ } else {
919
+ return null;
920
+ }
921
+ }
922
+ function ActionComponent({ action }) {
923
+ const {
924
+ label,
925
+ icon,
926
+ iconAfter,
927
+ children,
928
+ href,
929
+ onClick,
930
+ className: actionClassName,
931
+ ...pressableProps
932
+ } = action;
933
+ return /* @__PURE__ */ jsxRuntime.jsx(
934
+ Pressable,
935
+ {
936
+ href,
937
+ onClick,
938
+ asButton: action.asButton ?? true,
939
+ className: actionClassName,
940
+ ...pressableProps,
941
+ children: children ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
942
+ icon,
943
+ label,
944
+ iconAfter
945
+ ] })
946
+ }
947
+ );
948
+ }
885
949
  function TestimonialsSimpleGrid({
886
950
  testimonials,
887
951
  testimonialsSlot,
@@ -901,7 +965,10 @@ function TestimonialsSimpleGrid({
901
965
  containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
902
966
  spacing = "xl",
903
967
  pattern,
904
- patternOpacity
968
+ patternOpacity,
969
+ actions,
970
+ actionsSlot,
971
+ actionsClassName
905
972
  }) {
906
973
  const getAuthorName = React3.useCallback((testimonial) => {
907
974
  if (typeof testimonial.author === "string") return testimonial.author;
@@ -923,7 +990,7 @@ function TestimonialsSimpleGrid({
923
990
  "div",
924
991
  {
925
992
  className: cn(
926
- "grid gap-4 md:gap-6 lg:gap-8 md:grid-cols-2 lg:grid-cols-3",
993
+ "grid gap-8 md:gap-6 lg:gap-8 grid-cols-1 md:grid-cols-2 lg:grid-cols-3",
927
994
  gridClassName
928
995
  ),
929
996
  children: testimonials.map((testimonial, index) => {
@@ -935,8 +1002,8 @@ function TestimonialsSimpleGrid({
935
1002
  className: cn(
936
1003
  "bg-card text-card-foreground",
937
1004
  "flex flex-col gap-6",
938
- testimonial.linkConfig?.href ? "cursor-pointer hover:bg-black hover:text-white transition-all duration-500" : "",
939
- "rounded-2xl py-0 shadow-xl group",
1005
+ testimonial.linkConfig?.href ? "cursor-pointer hover:opacity-75 transition-all duration-500" : "opacity-100",
1006
+ "rounded-2xl py-0 shadow-xl group overflow-hidden",
940
1007
  "ring-4 ring-ring",
941
1008
  cardClassName
942
1009
  ),
@@ -987,7 +1054,7 @@ function TestimonialsSimpleGrid({
987
1054
  ] })
988
1055
  }
989
1056
  ),
990
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-6 md: pt-8 px-6 md:px-8", children: testimonial.quote && (typeof testimonial.quote === "string" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm leading-relaxed", children: testimonial.quote }) : testimonial.quote) })
1057
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-6 md:pt-8 px-6 md:px-8", children: testimonial.quote && (typeof testimonial.quote === "string" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-base leading-relaxed", children: testimonial.quote }) : testimonial.quote) })
991
1058
  ]
992
1059
  }
993
1060
  )
@@ -1046,7 +1113,16 @@ function TestimonialsSimpleGrid({
1046
1113
  ]
1047
1114
  }
1048
1115
  ),
1049
- renderedTestimonials
1116
+ renderedTestimonials,
1117
+ /* @__PURE__ */ jsxRuntime.jsx(
1118
+ BlockActions,
1119
+ {
1120
+ actions,
1121
+ actionsSlot,
1122
+ actionsClassName: cn("mt-8 md:mt-12 justify-center", actionsClassName),
1123
+ mobileConfig: { width: "full", position: "center" }
1124
+ }
1125
+ )
1050
1126
  ]
1051
1127
  }
1052
1128
  );
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { S as SectionBackground, s as SectionSpacing, t as PatternName } from './community-initiatives-B8KCpwXH.cjs';
3
+ import { ActionConfig } from '@page-speed/maps/components/geo-map';
3
4
  import { T as TestimonialItem } from './blocks-BtDAbw8d.cjs';
4
5
  import 'react/jsx-runtime';
5
6
  import 'class-variance-authority';
@@ -84,6 +85,18 @@ interface TestimonialsSimpleGridProps {
84
85
  * Additional CSS classes for the card content
85
86
  */
86
87
  cardContentClassName?: string;
88
+ /**
89
+ * Array of action configurations for CTA buttons
90
+ */
91
+ actions?: ActionConfig[];
92
+ /**
93
+ * Custom slot for rendering actions (overrides actions array)
94
+ */
95
+ actionsSlot?: React.ReactNode;
96
+ /**
97
+ * Additional CSS classes for the actions container
98
+ */
99
+ actionsClassName?: string;
87
100
  }
88
101
  /**
89
102
  * TestimonialsSimpleGrid - A clean, straightforward grid of testimonial cards with
@@ -112,6 +125,6 @@ interface TestimonialsSimpleGridProps {
112
125
  * />
113
126
  * ```
114
127
  */
115
- declare function TestimonialsSimpleGrid({ testimonials, testimonialsSlot, heading, description, columns, className, headerClassName, headingClassName, descriptionClassName, cardContentClassName, gridClassName, cardClassName, quoteClassName, authorClassName, background, containerClassName, spacing, pattern, patternOpacity, }: TestimonialsSimpleGridProps): React.JSX.Element;
128
+ declare function TestimonialsSimpleGrid({ testimonials, testimonialsSlot, heading, description, columns, className, headerClassName, headingClassName, descriptionClassName, cardContentClassName, gridClassName, cardClassName, quoteClassName, authorClassName, background, containerClassName, spacing, pattern, patternOpacity, actions, actionsSlot, actionsClassName, }: TestimonialsSimpleGridProps): React.JSX.Element;
116
129
 
117
130
  export { TestimonialsSimpleGrid, type TestimonialsSimpleGridProps };
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { S as SectionBackground, s as SectionSpacing, t as PatternName } from './community-initiatives-rTRuDt0r.js';
3
+ import { ActionConfig } from '@page-speed/maps/components/geo-map';
3
4
  import { T as TestimonialItem } from './blocks-BlWXj9GI.js';
4
5
  import 'react/jsx-runtime';
5
6
  import 'class-variance-authority';
@@ -84,6 +85,18 @@ interface TestimonialsSimpleGridProps {
84
85
  * Additional CSS classes for the card content
85
86
  */
86
87
  cardContentClassName?: string;
88
+ /**
89
+ * Array of action configurations for CTA buttons
90
+ */
91
+ actions?: ActionConfig[];
92
+ /**
93
+ * Custom slot for rendering actions (overrides actions array)
94
+ */
95
+ actionsSlot?: React.ReactNode;
96
+ /**
97
+ * Additional CSS classes for the actions container
98
+ */
99
+ actionsClassName?: string;
87
100
  }
88
101
  /**
89
102
  * TestimonialsSimpleGrid - A clean, straightforward grid of testimonial cards with
@@ -112,6 +125,6 @@ interface TestimonialsSimpleGridProps {
112
125
  * />
113
126
  * ```
114
127
  */
115
- declare function TestimonialsSimpleGrid({ testimonials, testimonialsSlot, heading, description, columns, className, headerClassName, headingClassName, descriptionClassName, cardContentClassName, gridClassName, cardClassName, quoteClassName, authorClassName, background, containerClassName, spacing, pattern, patternOpacity, }: TestimonialsSimpleGridProps): React.JSX.Element;
128
+ declare function TestimonialsSimpleGrid({ testimonials, testimonialsSlot, heading, description, columns, className, headerClassName, headingClassName, descriptionClassName, cardContentClassName, gridClassName, cardClassName, quoteClassName, authorClassName, background, containerClassName, spacing, pattern, patternOpacity, actions, actionsSlot, actionsClassName, }: TestimonialsSimpleGridProps): React.JSX.Element;
116
129
 
117
130
  export { TestimonialsSimpleGrid, type TestimonialsSimpleGridProps };