@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,22 +1,19 @@
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 { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { Icon } from '@page-speed/icon';
7
- import { jsx, jsxs } from 'react/jsx-runtime';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
8
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
9
+ import { cva } from 'class-variance-authority';
9
10
 
10
11
  // components/blocks/testimonials/testimonials-grid-add-review.tsx
11
12
  function cn(...inputs) {
12
13
  return twMerge(clsx(inputs));
13
14
  }
14
- function getNestedCardTextColor(parentBg, options) {
15
- const isDark = parentBg === "dark" || parentBg === "secondary" || parentBg === "primary";
16
- return isDark ? "text-foreground" : "";
17
- }
18
15
  var DEFAULT_ICON_API_KEY = "au382bi7fsh96w9h9xlrnat2jglx";
19
- var DynamicIcon = React.memo(function DynamicIcon2({
16
+ var DynamicIcon = React4.memo(function DynamicIcon2({
20
17
  apiKey,
21
18
  ...props
22
19
  }) {
@@ -117,7 +114,7 @@ var maxWidthStyles = {
117
114
  "4xl": "max-w-[1536px]",
118
115
  full: "max-w-full"
119
116
  };
120
- var Container = React__default.forwardRef(
117
+ var Container = React4__default.forwardRef(
121
118
  ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
122
119
  const Component = as;
123
120
  return /* @__PURE__ */ jsx(
@@ -423,7 +420,7 @@ var spacingStyles = {
423
420
  };
424
421
  var predefinedSpacings = ["none", "sm", "md", "lg", "xl", "hero"];
425
422
  var isPredefinedSpacing = (spacing) => predefinedSpacings.includes(spacing);
426
- var Section = React__default.forwardRef(
423
+ var Section = React4__default.forwardRef(
427
424
  ({
428
425
  id,
429
426
  title,
@@ -484,6 +481,488 @@ var Section = React__default.forwardRef(
484
481
  }
485
482
  );
486
483
  Section.displayName = "Section";
484
+ var baseStyles = [
485
+ // Layout
486
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap shrink-0",
487
+ // Typography - using CSS variables with sensible defaults
488
+ "font-[var(--button-font-family,inherit)]",
489
+ "font-[var(--button-font-weight,500)]",
490
+ "tracking-[var(--button-letter-spacing,0)]",
491
+ "leading-[var(--button-line-height,1.25)]",
492
+ "[text-transform:var(--button-text-transform,none)]",
493
+ "text-sm",
494
+ // Border radius
495
+ "rounded-[var(--button-radius,var(--radius,0.375rem))]",
496
+ // Smooth transition - using [transition:...] to set full shorthand property (not just transition-property)
497
+ "[transition:var(--button-transition,all_250ms_cubic-bezier(0.4,0,0.2,1))]",
498
+ // Box shadow (master level) - using [box-shadow:...] for complex multi-value shadows
499
+ "[box-shadow:var(--button-shadow,none)]",
500
+ "hover:[box-shadow:var(--button-shadow-hover,var(--button-shadow,none))]",
501
+ // Disabled state
502
+ "disabled:pointer-events-none disabled:opacity-50",
503
+ // SVG handling
504
+ "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0",
505
+ // Focus styles
506
+ "outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
507
+ // Invalid state
508
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive"
509
+ ].join(" ");
510
+ var buttonVariants = cva(baseStyles, {
511
+ variants: {
512
+ variant: {
513
+ // Default (Primary) variant - full customization
514
+ default: [
515
+ "bg-[var(--button-default-bg,hsl(var(--primary)))]",
516
+ "text-[var(--button-default-fg,hsl(var(--primary-foreground)))]",
517
+ "border-[length:var(--button-default-border-width,0px)]",
518
+ "border-[color:var(--button-default-border,transparent)]",
519
+ "[box-shadow:var(--button-default-shadow,var(--button-shadow,none))]",
520
+ "hover:bg-[var(--button-default-hover-bg,hsl(var(--primary)/0.9))]",
521
+ "hover:text-[var(--button-default-hover-fg,var(--button-default-fg,hsl(var(--primary-foreground))))]",
522
+ "hover:border-[color:var(--button-default-hover-border,var(--button-default-border,transparent))]",
523
+ "hover:[box-shadow:var(--button-default-shadow-hover,var(--button-shadow-hover,var(--button-default-shadow,var(--button-shadow,none))))]"
524
+ ].join(" "),
525
+ // Destructive variant - full customization
526
+ destructive: [
527
+ "bg-[var(--button-destructive-bg,hsl(var(--destructive)))]",
528
+ "text-[var(--button-destructive-fg,white)]",
529
+ "border-[length:var(--button-destructive-border-width,0px)]",
530
+ "border-[color:var(--button-destructive-border,transparent)]",
531
+ "[box-shadow:var(--button-destructive-shadow,var(--button-shadow,none))]",
532
+ "hover:bg-[var(--button-destructive-hover-bg,hsl(var(--destructive)/0.9))]",
533
+ "hover:text-[var(--button-destructive-hover-fg,var(--button-destructive-fg,white))]",
534
+ "hover:border-[color:var(--button-destructive-hover-border,var(--button-destructive-border,transparent))]",
535
+ "hover:[box-shadow:var(--button-destructive-shadow-hover,var(--button-shadow-hover,var(--button-destructive-shadow,var(--button-shadow,none))))]",
536
+ "focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
537
+ "dark:bg-destructive/60"
538
+ ].join(" "),
539
+ // Outline variant - full customization with proper border handling
540
+ outline: [
541
+ "bg-[var(--button-outline-bg,hsl(var(--background)))]",
542
+ "text-[var(--button-outline-fg,inherit)]",
543
+ "border-[length:var(--button-outline-border-width,1px)]",
544
+ "border-[color:var(--button-outline-border,hsl(var(--border)))]",
545
+ "[box-shadow:var(--button-outline-shadow,var(--button-shadow,0_1px_2px_0_rgb(0_0_0/0.05)))]",
546
+ "hover:bg-[var(--button-outline-hover-bg,hsl(var(--accent)))]",
547
+ "hover:text-[var(--button-outline-hover-fg,hsl(var(--accent-foreground)))]",
548
+ "hover:border-[color:var(--button-outline-hover-border,var(--button-outline-border,hsl(var(--border))))]",
549
+ "hover:[box-shadow:var(--button-outline-shadow-hover,var(--button-shadow-hover,var(--button-outline-shadow,var(--button-shadow,none))))]",
550
+ "dark:bg-input/30 dark:border-input dark:hover:bg-input/50"
551
+ ].join(" "),
552
+ // Secondary variant - full customization
553
+ secondary: [
554
+ "bg-[var(--button-secondary-bg,hsl(var(--secondary)))]",
555
+ "text-[var(--button-secondary-fg,hsl(var(--secondary-foreground)))]",
556
+ "border-[length:var(--button-secondary-border-width,0px)]",
557
+ "border-[color:var(--button-secondary-border,transparent)]",
558
+ "[box-shadow:var(--button-secondary-shadow,var(--button-shadow,none))]",
559
+ "hover:bg-[var(--button-secondary-hover-bg,hsl(var(--secondary)/0.8))]",
560
+ "hover:text-[var(--button-secondary-hover-fg,var(--button-secondary-fg,hsl(var(--secondary-foreground))))]",
561
+ "hover:border-[color:var(--button-secondary-hover-border,var(--button-secondary-border,transparent))]",
562
+ "hover:[box-shadow:var(--button-secondary-shadow-hover,var(--button-shadow-hover,var(--button-secondary-shadow,var(--button-shadow,none))))]"
563
+ ].join(" "),
564
+ // Ghost variant - full customization
565
+ ghost: [
566
+ "bg-[var(--button-ghost-bg,transparent)]",
567
+ "text-[var(--button-ghost-fg,inherit)]",
568
+ "border-[length:var(--button-ghost-border-width,0px)]",
569
+ "border-[color:var(--button-ghost-border,transparent)]",
570
+ "[box-shadow:var(--button-ghost-shadow,var(--button-shadow,none))]",
571
+ "hover:bg-[var(--button-ghost-hover-bg,hsl(var(--accent)))]",
572
+ "hover:text-[var(--button-ghost-hover-fg,hsl(var(--accent-foreground)))]",
573
+ "hover:border-[color:var(--button-ghost-hover-border,var(--button-ghost-border,transparent))]",
574
+ "hover:[box-shadow:var(--button-ghost-shadow-hover,var(--button-shadow-hover,var(--button-ghost-shadow,var(--button-shadow,none))))]",
575
+ "dark:hover:bg-accent/50"
576
+ ].join(" "),
577
+ // Link variant - full customization
578
+ link: [
579
+ "bg-[var(--button-link-bg,transparent)]",
580
+ "text-[var(--button-link-fg,hsl(var(--primary)))]",
581
+ "border-[length:var(--button-link-border-width,0px)]",
582
+ "border-[color:var(--button-link-border,transparent)]",
583
+ "[box-shadow:var(--button-link-shadow,none)]",
584
+ "hover:bg-[var(--button-link-hover-bg,transparent)]",
585
+ "hover:text-[var(--button-link-hover-fg,var(--button-link-fg,hsl(var(--primary))))]",
586
+ "hover:[box-shadow:var(--button-link-shadow-hover,none)]",
587
+ "underline-offset-4 hover:underline"
588
+ ].join(" ")
589
+ },
590
+ size: {
591
+ default: [
592
+ "h-[var(--button-height-md,2.25rem)]",
593
+ "px-[var(--button-padding-x-md,1rem)]",
594
+ "py-[var(--button-padding-y-md,0.5rem)]",
595
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
596
+ ].join(" "),
597
+ sm: [
598
+ "h-[var(--button-height-sm,2rem)]",
599
+ "px-[var(--button-padding-x-sm,0.75rem)]",
600
+ "py-[var(--button-padding-y-sm,0.25rem)]",
601
+ "gap-1.5",
602
+ "has-[>svg]:px-[calc(var(--button-padding-x-sm,0.75rem)*0.83)]"
603
+ ].join(" "),
604
+ md: [
605
+ "h-[var(--button-height-md,2.25rem)]",
606
+ "px-[var(--button-padding-x-md,1rem)]",
607
+ "py-[var(--button-padding-y-md,0.5rem)]",
608
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
609
+ ].join(" "),
610
+ lg: [
611
+ "h-[var(--button-height-lg,2.5rem)]",
612
+ "px-[var(--button-padding-x-lg,1.5rem)]",
613
+ "py-[var(--button-padding-y-lg,0.5rem)]",
614
+ "has-[>svg]:px-[calc(var(--button-padding-x-lg,1.5rem)*0.67)]"
615
+ ].join(" "),
616
+ icon: "size-[var(--button-height-md,2.25rem)]",
617
+ "icon-sm": "size-[var(--button-height-sm,2rem)]",
618
+ "icon-lg": "size-[var(--button-height-lg,2.5rem)]"
619
+ }
620
+ },
621
+ defaultVariants: {
622
+ variant: "default",
623
+ size: "default"
624
+ }
625
+ });
626
+ function normalizePhoneNumber(input) {
627
+ const trimmed = input.trim();
628
+ if (trimmed.toLowerCase().startsWith("tel:")) {
629
+ return trimmed;
630
+ }
631
+ const match = trimmed.match(/^[\s\+\-\(\)]*(\d[\d\s\-\(\)\.]*\d)[\s\-]*(x|ext\.?|extension)?[\s\-]*(\d+)?$/i);
632
+ if (match) {
633
+ const mainNumber = match[1].replace(/[\s\-\(\)\.]/g, "");
634
+ const extension = match[3];
635
+ const normalized = mainNumber.length >= 10 && !trimmed.startsWith("+") ? `+${mainNumber}` : mainNumber;
636
+ const withExtension = extension ? `${normalized};ext=${extension}` : normalized;
637
+ return `tel:${withExtension}`;
638
+ }
639
+ const cleaned = trimmed.replace(/[\s\-\(\)\.]/g, "");
640
+ return `tel:${cleaned}`;
641
+ }
642
+ function normalizeEmail(input) {
643
+ const trimmed = input.trim();
644
+ if (trimmed.toLowerCase().startsWith("mailto:")) {
645
+ return trimmed;
646
+ }
647
+ return `mailto:${trimmed}`;
648
+ }
649
+ function isEmail(input) {
650
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
651
+ return emailRegex.test(input.trim());
652
+ }
653
+ function isPhoneNumber(input) {
654
+ const trimmed = input.trim();
655
+ if (trimmed.toLowerCase().startsWith("tel:")) {
656
+ return true;
657
+ }
658
+ const phoneRegex = /^[\s\+\-\(\)]*\d[\d\s\-\(\)\.]*\d[\s\-]*(x|ext\.?|extension)?[\s\-]*\d*$/i;
659
+ return phoneRegex.test(trimmed);
660
+ }
661
+ function isInternalUrl(href) {
662
+ if (typeof window === "undefined") {
663
+ return href.startsWith("/") && !href.startsWith("//");
664
+ }
665
+ const trimmed = href.trim();
666
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
667
+ return true;
668
+ }
669
+ try {
670
+ const url = new URL(trimmed, window.location.href);
671
+ const currentOrigin = window.location.origin;
672
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
673
+ return normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin);
674
+ } catch {
675
+ return false;
676
+ }
677
+ }
678
+ function toRelativePath(href) {
679
+ if (typeof window === "undefined") {
680
+ return href;
681
+ }
682
+ const trimmed = href.trim();
683
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
684
+ return trimmed;
685
+ }
686
+ try {
687
+ const url = new URL(trimmed, window.location.href);
688
+ const currentOrigin = window.location.origin;
689
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
690
+ if (normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin)) {
691
+ return url.pathname + url.search + url.hash;
692
+ }
693
+ } catch {
694
+ }
695
+ return trimmed;
696
+ }
697
+ function useNavigation({
698
+ href,
699
+ onClick
700
+ } = {}) {
701
+ const linkType = React4.useMemo(() => {
702
+ if (!href || href.trim() === "") {
703
+ return onClick ? "none" : "none";
704
+ }
705
+ const trimmed = href.trim();
706
+ if (trimmed.toLowerCase().startsWith("mailto:") || isEmail(trimmed)) {
707
+ return "mailto";
708
+ }
709
+ if (trimmed.toLowerCase().startsWith("tel:") || isPhoneNumber(trimmed)) {
710
+ return "tel";
711
+ }
712
+ if (isInternalUrl(trimmed)) {
713
+ return "internal";
714
+ }
715
+ try {
716
+ new URL(trimmed, typeof window !== "undefined" ? window.location.href : "http://localhost");
717
+ return "external";
718
+ } catch {
719
+ return "internal";
720
+ }
721
+ }, [href, onClick]);
722
+ const normalizedHref = React4.useMemo(() => {
723
+ if (!href || href.trim() === "") {
724
+ return void 0;
725
+ }
726
+ const trimmed = href.trim();
727
+ switch (linkType) {
728
+ case "tel":
729
+ return normalizePhoneNumber(trimmed);
730
+ case "mailto":
731
+ return normalizeEmail(trimmed);
732
+ case "internal":
733
+ return toRelativePath(trimmed);
734
+ case "external":
735
+ return trimmed;
736
+ default:
737
+ return trimmed;
738
+ }
739
+ }, [href, linkType]);
740
+ const target = React4.useMemo(() => {
741
+ switch (linkType) {
742
+ case "external":
743
+ return "_blank";
744
+ case "internal":
745
+ return "_self";
746
+ case "mailto":
747
+ case "tel":
748
+ return void 0;
749
+ default:
750
+ return void 0;
751
+ }
752
+ }, [linkType]);
753
+ const rel = React4.useMemo(() => {
754
+ if (linkType === "external") {
755
+ return "noopener noreferrer";
756
+ }
757
+ return void 0;
758
+ }, [linkType]);
759
+ const isExternal = linkType === "external";
760
+ const isInternal = linkType === "internal";
761
+ const shouldUseRouter = isInternal && typeof normalizedHref === "string" && normalizedHref.startsWith("/");
762
+ const handleClick = React4.useCallback(
763
+ (event) => {
764
+ if (onClick) {
765
+ try {
766
+ onClick(event);
767
+ } catch (error) {
768
+ console.error("Error in user onClick handler:", error);
769
+ }
770
+ }
771
+ if (event.defaultPrevented) {
772
+ return;
773
+ }
774
+ if (shouldUseRouter && normalizedHref && event.button === 0 && // left-click only
775
+ !event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey) {
776
+ if (typeof window !== "undefined") {
777
+ const handler = window.__opensiteNavigationHandler;
778
+ if (typeof handler === "function") {
779
+ try {
780
+ const handled = handler(normalizedHref, event.nativeEvent || event);
781
+ if (handled !== false) {
782
+ event.preventDefault();
783
+ }
784
+ } catch (error) {
785
+ console.error("Error in navigation handler:", error);
786
+ }
787
+ }
788
+ }
789
+ }
790
+ },
791
+ [onClick, shouldUseRouter, normalizedHref]
792
+ );
793
+ return {
794
+ linkType,
795
+ normalizedHref,
796
+ target,
797
+ rel,
798
+ isExternal,
799
+ isInternal,
800
+ shouldUseRouter,
801
+ handleClick
802
+ };
803
+ }
804
+ var Pressable = React4.forwardRef(
805
+ ({
806
+ children,
807
+ className,
808
+ href,
809
+ onClick,
810
+ variant,
811
+ size,
812
+ asButton = false,
813
+ fallbackComponentType = "span",
814
+ componentType,
815
+ "aria-label": ariaLabel,
816
+ "aria-describedby": ariaDescribedby,
817
+ id,
818
+ ...props
819
+ }, ref) => {
820
+ const navigation = useNavigation({ href, onClick });
821
+ const {
822
+ normalizedHref,
823
+ target,
824
+ rel,
825
+ linkType,
826
+ isInternal,
827
+ handleClick
828
+ } = navigation;
829
+ const shouldRenderLink = normalizedHref && linkType !== "none";
830
+ const shouldRenderButton = !shouldRenderLink && onClick;
831
+ const effectiveComponentType = componentType || (shouldRenderLink ? "a" : shouldRenderButton ? "button" : fallbackComponentType);
832
+ const finalComponentType = isInternal && shouldRenderLink ? "a" : effectiveComponentType;
833
+ const shouldApplyButtonStyles = asButton || variant || size;
834
+ const combinedClassName = cn(
835
+ shouldApplyButtonStyles && buttonVariants({ variant, size }),
836
+ className
837
+ );
838
+ const dataProps = Object.fromEntries(
839
+ Object.entries(props).filter(([key]) => key.startsWith("data-"))
840
+ );
841
+ const buttonDataAttributes = shouldApplyButtonStyles ? {
842
+ "data-slot": "button",
843
+ "data-variant": variant ?? "default",
844
+ "data-size": size ?? "default"
845
+ } : {};
846
+ const commonProps = {
847
+ className: combinedClassName,
848
+ onClick: handleClick,
849
+ "aria-label": ariaLabel,
850
+ "aria-describedby": ariaDescribedby,
851
+ id,
852
+ ...dataProps,
853
+ ...buttonDataAttributes
854
+ };
855
+ if (finalComponentType === "a" && shouldRenderLink) {
856
+ return /* @__PURE__ */ jsx(
857
+ "a",
858
+ {
859
+ ref,
860
+ href: normalizedHref,
861
+ target,
862
+ rel,
863
+ ...commonProps,
864
+ ...props,
865
+ children
866
+ }
867
+ );
868
+ }
869
+ if (finalComponentType === "button") {
870
+ return /* @__PURE__ */ jsx(
871
+ "button",
872
+ {
873
+ ref,
874
+ type: props.type || "button",
875
+ ...commonProps,
876
+ ...props,
877
+ children
878
+ }
879
+ );
880
+ }
881
+ if (finalComponentType === "div") {
882
+ return /* @__PURE__ */ jsx(
883
+ "div",
884
+ {
885
+ ref,
886
+ ...commonProps,
887
+ children
888
+ }
889
+ );
890
+ }
891
+ return /* @__PURE__ */ jsx(
892
+ "span",
893
+ {
894
+ ref,
895
+ ...commonProps,
896
+ children
897
+ }
898
+ );
899
+ }
900
+ );
901
+ Pressable.displayName = "Pressable";
902
+ var MOBILE_CLASSES = {
903
+ "fit-left": "items-start md:items-center",
904
+ "fit-center": "items-center",
905
+ "fit-right": "items-end md:items-center",
906
+ "full-left": "items-stretch md:items-center",
907
+ "full-center": "items-stretch md:items-center",
908
+ "full-right": "items-stretch md:items-center"
909
+ };
910
+ function BlockActions({
911
+ mobileConfig,
912
+ actionsClassName,
913
+ verticalSpacing = "mt-4 md:mt-8",
914
+ actions,
915
+ actionsSlot
916
+ }) {
917
+ const width = mobileConfig?.width ?? "full";
918
+ const position = mobileConfig?.position ?? "center";
919
+ const mobileLayoutClass = MOBILE_CLASSES[`${width}-${position}`];
920
+ if (actionsSlot) {
921
+ return /* @__PURE__ */ jsx("div", { children: actionsSlot });
922
+ } else if (actions && actions?.length > 0) {
923
+ return /* @__PURE__ */ jsx(
924
+ "div",
925
+ {
926
+ className: cn(
927
+ "flex flex-col md:flex-row flex-wrap gap-4",
928
+ mobileLayoutClass,
929
+ actionsClassName,
930
+ verticalSpacing
931
+ ),
932
+ children: actions.map((action, index) => /* @__PURE__ */ jsx(ActionComponent, { action }, index))
933
+ }
934
+ );
935
+ } else {
936
+ return null;
937
+ }
938
+ }
939
+ function ActionComponent({ action }) {
940
+ const {
941
+ label,
942
+ icon,
943
+ iconAfter,
944
+ children,
945
+ href,
946
+ onClick,
947
+ className: actionClassName,
948
+ ...pressableProps
949
+ } = action;
950
+ return /* @__PURE__ */ jsx(
951
+ Pressable,
952
+ {
953
+ href,
954
+ onClick,
955
+ asButton: action.asButton ?? true,
956
+ className: actionClassName,
957
+ ...pressableProps,
958
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
959
+ icon,
960
+ label,
961
+ iconAfter
962
+ ] })
963
+ }
964
+ );
965
+ }
487
966
  function TestimonialsGridAddReview({
488
967
  reviews,
489
968
  reviewsSlot,
@@ -498,17 +977,28 @@ function TestimonialsGridAddReview({
498
977
  descriptionClassName,
499
978
  gridClassName,
500
979
  cardClassName,
980
+ quoteClassName,
501
981
  addReviewCardClassName,
502
982
  authorClassName,
503
983
  background,
504
- spacing,
984
+ spacing = "lg",
985
+ containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
505
986
  pattern,
506
- patternOpacity
987
+ patternOpacity,
988
+ actions,
989
+ actionsSlot,
990
+ actionsClassName
507
991
  }) {
508
- const getAuthorName = useCallback((review) => {
509
- if (typeof review.author === "string") return review.author;
992
+ const getAuthorName = useCallback((testimonial) => {
993
+ if (typeof testimonial.author === "string") return testimonial.author;
510
994
  return "";
511
995
  }, []);
996
+ const getAvatarSrc = useCallback(
997
+ (testimonial) => {
998
+ return testimonial.avatarSrc || testimonial.avatar?.src;
999
+ },
1000
+ []
1001
+ );
512
1002
  const getInitials = useCallback((name) => {
513
1003
  return name.split(" ").map((n) => n[0]).join("");
514
1004
  }, []);
@@ -526,17 +1016,14 @@ function TestimonialsGridAddReview({
526
1016
  Card,
527
1017
  {
528
1018
  className: cn(
529
- "flex cursor-pointer items-center justify-center border-2 border-dashed transition-colors hover:border-primary hover:bg-muted/50",
1019
+ "flex cursor-pointer items-center justify-center border-2 border-dashed transition-all duration-500 opacity-100 hover:border-primary hover:opacity-75",
530
1020
  addReviewCardClassName
531
1021
  ),
532
1022
  onClick: onAddReview,
533
1023
  children: /* @__PURE__ */ jsxs(
534
1024
  CardContent,
535
1025
  {
536
- className: cn(
537
- "flex flex-col items-center gap-3 py-12 text-center",
538
- getNestedCardTextColor(background)
539
- ),
1026
+ className: cn("flex flex-col items-center gap-3 py-12 text-center"),
540
1027
  children: [
541
1028
  /* @__PURE__ */ jsx("div", { className: "flex size-12 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsx(
542
1029
  DynamicIcon,
@@ -553,19 +1040,58 @@ function TestimonialsGridAddReview({
553
1040
  )
554
1041
  }
555
1042
  ),
556
- reviews?.map((review, index) => {
557
- const authorName = getAuthorName(review);
558
- return /* @__PURE__ */ jsx(Card, { className: cardClassName, children: /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4 p-6", children: [
559
- /* @__PURE__ */ jsx(StarRating, { rating: review.rating }),
560
- review.content && (typeof review.content === "string" ? /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed", children: review.content }) : review.content),
561
- /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-3", authorClassName), children: [
562
- /* @__PURE__ */ jsxs(Avatar, { className: "size-8", children: [
563
- /* @__PURE__ */ jsx(AvatarImage, { src: review.avatarSrc, alt: authorName }),
564
- /* @__PURE__ */ jsx(AvatarFallback, { className: "text-xs", children: getInitials(authorName) })
565
- ] }),
566
- review.author && (typeof review.author === "string" ? /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: review.author }) : review.author)
567
- ] })
568
- ] }) }, index);
1043
+ reviews?.map((testimonial, index) => {
1044
+ const authorName = getAuthorName(testimonial);
1045
+ const avatarSrc = getAvatarSrc(testimonial);
1046
+ return /* @__PURE__ */ jsx(Card, { className: cardClassName, children: /* @__PURE__ */ jsx(CardContent, { className: "p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-12 justify-between", children: [
1047
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-4", children: [
1048
+ /* @__PURE__ */ jsx(StarRating, { rating: 5, size: 20 }),
1049
+ testimonial.quote && (typeof testimonial.quote === "string" ? /* @__PURE__ */ jsxs(
1050
+ "p",
1051
+ {
1052
+ className: cn(
1053
+ "mb-6 text-sm leading-relaxed",
1054
+ quoteClassName
1055
+ ),
1056
+ children: [
1057
+ "\u201C",
1058
+ testimonial.quote,
1059
+ "\u201D"
1060
+ ]
1061
+ }
1062
+ ) : /* @__PURE__ */ jsx("div", { className: cn("mb-6", quoteClassName), children: testimonial.quote }))
1063
+ ] }),
1064
+ /* @__PURE__ */ jsxs(
1065
+ "div",
1066
+ {
1067
+ className: cn("flex items-center gap-4", authorClassName),
1068
+ children: [
1069
+ /* @__PURE__ */ jsxs(Avatar, { className: "size-14 ring-4 ring-primary shadow-lg", children: [
1070
+ /* @__PURE__ */ jsx(AvatarImage, { src: avatarSrc, alt: authorName }),
1071
+ /* @__PURE__ */ jsx(AvatarFallback, { children: getInitials(authorName) })
1072
+ ] }),
1073
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-1", children: [
1074
+ /* @__PURE__ */ jsxs("div", { className: "space-y-0", children: [
1075
+ testimonial.author && (typeof testimonial.author === "string" ? /* @__PURE__ */ jsx("p", { className: "text-base font-medium", children: testimonial.author }) : testimonial.author),
1076
+ testimonial.role && (typeof testimonial.role === "string" ? /* @__PURE__ */ jsx("p", { className: "text-sm opacity-75", children: testimonial.role }) : testimonial.role)
1077
+ ] }),
1078
+ testimonial.linkConfig?.href && /* @__PURE__ */ jsx(
1079
+ Pressable,
1080
+ {
1081
+ href: testimonial.linkConfig.href,
1082
+ className: cn(
1083
+ "text-sm transition-all duration-500",
1084
+ "hover:underline hover:underline-offset-4",
1085
+ testimonial.linkConfig.className
1086
+ ),
1087
+ children: testimonial.linkConfig.label
1088
+ }
1089
+ )
1090
+ ] })
1091
+ ]
1092
+ }
1093
+ )
1094
+ ] }) }) }, index);
569
1095
  })
570
1096
  ]
571
1097
  }
@@ -591,36 +1117,49 @@ function TestimonialsGridAddReview({
591
1117
  pattern,
592
1118
  patternOpacity,
593
1119
  className,
1120
+ containerClassName,
594
1121
  children: [
595
1122
  /* @__PURE__ */ jsxs(
596
1123
  "div",
597
1124
  {
598
- className: cn("mx-auto mb-12 max-w-2xl text-center", headerClassName),
1125
+ className: cn(
1126
+ "mx-auto mb-12 max-w-full md:max-w-2xl text-center",
1127
+ headerClassName
1128
+ ),
599
1129
  children: [
600
1130
  heading && (typeof heading === "string" ? /* @__PURE__ */ jsx(
601
1131
  "h2",
602
1132
  {
603
1133
  className: cn(
604
- "text-3xl font-semibold tracking-tight md:text-4xl",
1134
+ "text-3xl font-semibold tracking-tight md:text-4xl text-pretty",
605
1135
  headingClassName
606
1136
  ),
607
1137
  children: heading
608
1138
  }
609
- ) : /* @__PURE__ */ jsx("div", { className: headingClassName, children: heading })),
1139
+ ) : heading),
610
1140
  description && (typeof description === "string" ? /* @__PURE__ */ jsx(
611
1141
  "p",
612
1142
  {
613
1143
  className: cn(
614
- "mt-4 text-lg text-muted-foreground",
1144
+ "mt-2 md:mt-4 text-lg text-balance",
615
1145
  descriptionClassName
616
1146
  ),
617
1147
  children: description
618
1148
  }
619
- ) : /* @__PURE__ */ jsx("div", { className: cn("mt-4", descriptionClassName), children: description }))
1149
+ ) : description)
620
1150
  ]
621
1151
  }
622
1152
  ),
623
- renderedReviews
1153
+ renderedReviews,
1154
+ /* @__PURE__ */ jsx(
1155
+ BlockActions,
1156
+ {
1157
+ actions,
1158
+ actionsSlot,
1159
+ actionsClassName: cn("mt-8 md:mt-12 justify-center", actionsClassName),
1160
+ mobileConfig: { width: "full", position: "center" }
1161
+ }
1162
+ )
624
1163
  ]
625
1164
  }
626
1165
  );