@opensite/ui 0.7.9 → 0.8.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.
@@ -1,15 +1,15 @@
1
1
  "use client";
2
+ import * as React6 from 'react';
3
+ import React6__default from 'react';
2
4
  import { clsx } from 'clsx';
3
5
  import { twMerge } from 'tailwind-merge';
4
6
  import { Slot } from '@radix-ui/react-slot';
5
7
  import { cva } from 'class-variance-authority';
6
8
  import { jsx, jsxs } from 'react/jsx-runtime';
7
- import * as React4 from 'react';
8
- import React4__default from 'react';
9
- import useEmblaCarousel from 'embla-carousel-react';
10
9
  import { Img } from '@page-speed/img';
10
+ import useEmblaCarousel from 'embla-carousel-react';
11
11
 
12
- // lib/utils.ts
12
+ // components/blocks/carousel/carousel-feature-badge.tsx
13
13
  function cn(...inputs) {
14
14
  return twMerge(clsx(inputs));
15
15
  }
@@ -120,7 +120,7 @@ function useNavigation({
120
120
  href,
121
121
  onClick
122
122
  } = {}) {
123
- const linkType = React4.useMemo(() => {
123
+ const linkType = React6.useMemo(() => {
124
124
  if (!href || href.trim() === "") {
125
125
  return onClick ? "none" : "none";
126
126
  }
@@ -141,7 +141,7 @@ function useNavigation({
141
141
  return "internal";
142
142
  }
143
143
  }, [href, onClick]);
144
- const normalizedHref = React4.useMemo(() => {
144
+ const normalizedHref = React6.useMemo(() => {
145
145
  if (!href || href.trim() === "") {
146
146
  return void 0;
147
147
  }
@@ -159,7 +159,7 @@ function useNavigation({
159
159
  return trimmed;
160
160
  }
161
161
  }, [href, linkType]);
162
- const target = React4.useMemo(() => {
162
+ const target = React6.useMemo(() => {
163
163
  switch (linkType) {
164
164
  case "external":
165
165
  return "_blank";
@@ -172,7 +172,7 @@ function useNavigation({
172
172
  return void 0;
173
173
  }
174
174
  }, [linkType]);
175
- const rel = React4.useMemo(() => {
175
+ const rel = React6.useMemo(() => {
176
176
  if (linkType === "external") {
177
177
  return "noopener noreferrer";
178
178
  }
@@ -181,7 +181,7 @@ function useNavigation({
181
181
  const isExternal = linkType === "external";
182
182
  const isInternal = linkType === "internal";
183
183
  const shouldUseRouter = isInternal && typeof normalizedHref === "string" && normalizedHref.startsWith("/");
184
- const handleClick = React4.useCallback(
184
+ const handleClick = React6.useCallback(
185
185
  (event) => {
186
186
  if (onClick) {
187
187
  try {
@@ -365,7 +365,7 @@ var buttonVariants = cva(baseStyles, {
365
365
  size: "default"
366
366
  }
367
367
  });
368
- var Pressable = React4.forwardRef(
368
+ var Pressable = React6.forwardRef(
369
369
  ({
370
370
  children,
371
371
  className,
@@ -471,10 +471,10 @@ function DynamicIcon({
471
471
  className,
472
472
  alt
473
473
  }) {
474
- const [svgContent, setSvgContent] = React4.useState(null);
475
- const [isLoading, setIsLoading] = React4.useState(true);
476
- const [error, setError] = React4.useState(null);
477
- const { url, iconName } = React4.useMemo(() => {
474
+ const [svgContent, setSvgContent] = React6.useState(null);
475
+ const [isLoading, setIsLoading] = React6.useState(true);
476
+ const [error, setError] = React6.useState(null);
477
+ const { url, iconName } = React6.useMemo(() => {
478
478
  const separator = name.includes("/") ? "/" : ":";
479
479
  const [prefix, iconName2] = name.split(separator);
480
480
  const baseUrl = `https://icons.opensite.ai/api/icon/${prefix}/${iconName2}?format=svg&width=${size}&height=${size}`;
@@ -483,7 +483,7 @@ function DynamicIcon({
483
483
  iconName: iconName2
484
484
  };
485
485
  }, [name, size]);
486
- React4.useEffect(() => {
486
+ React6.useEffect(() => {
487
487
  let isMounted = true;
488
488
  const fetchSvg = async () => {
489
489
  const cached = svgCache.get(url);
@@ -568,192 +568,49 @@ function processSvgForCurrentColor(svg) {
568
568
  );
569
569
  return processed;
570
570
  }
571
- var CarouselContext = React4.createContext(null);
572
- function useCarousel() {
573
- const context = React4.useContext(CarouselContext);
574
- if (!context) {
575
- throw new Error("useCarousel must be used within a <Carousel />");
576
- }
577
- return context;
578
- }
579
- function Carousel({
580
- orientation = "horizontal",
581
- opts,
582
- setApi,
583
- plugins,
571
+ function CarouselPagination({
572
+ onPrevious,
573
+ onNext,
574
+ canScrollPrevious = true,
575
+ canScrollNext = true,
576
+ iconSize = 24,
584
577
  className,
585
- children,
586
- ...props
578
+ buttonClassName,
579
+ previousIcon = "lucide/arrow-left",
580
+ nextIcon = "lucide/arrow-right",
581
+ previousAriaLabel = "Previous",
582
+ nextAriaLabel = "Next"
587
583
  }) {
588
- const [carouselRef, api] = useEmblaCarousel(
589
- {
590
- ...opts,
591
- axis: orientation === "horizontal" ? "x" : "y"
592
- },
593
- plugins
594
- );
595
- const [canScrollPrev, setCanScrollPrev] = React4.useState(false);
596
- const [canScrollNext, setCanScrollNext] = React4.useState(false);
597
- const onSelect = React4.useCallback((api2) => {
598
- if (!api2) return;
599
- setCanScrollPrev(api2.canScrollPrev());
600
- setCanScrollNext(api2.canScrollNext());
601
- }, []);
602
- const scrollPrev = React4.useCallback(() => {
603
- api?.scrollPrev();
604
- }, [api]);
605
- const scrollNext = React4.useCallback(() => {
606
- api?.scrollNext();
607
- }, [api]);
608
- const handleKeyDown = React4.useCallback(
609
- (event) => {
610
- if (event.key === "ArrowLeft") {
611
- event.preventDefault();
612
- scrollPrev();
613
- } else if (event.key === "ArrowRight") {
614
- event.preventDefault();
615
- scrollNext();
584
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex justify-end gap-2", className), children: [
585
+ /* @__PURE__ */ jsx(
586
+ Pressable,
587
+ {
588
+ onClick: onPrevious,
589
+ disabled: !canScrollPrevious,
590
+ "aria-label": previousAriaLabel,
591
+ asButton: true,
592
+ className: cn(
593
+ "relative z-40 flex h-10 w-10 items-center justify-center rounded-full disabled:opacity-50",
594
+ buttonClassName
595
+ ),
596
+ children: /* @__PURE__ */ jsx(DynamicIcon, { name: previousIcon, size: iconSize })
616
597
  }
617
- },
618
- [scrollPrev, scrollNext]
619
- );
620
- React4.useEffect(() => {
621
- if (!api || !setApi) return;
622
- setApi(api);
623
- }, [api, setApi]);
624
- React4.useEffect(() => {
625
- if (!api) return;
626
- onSelect(api);
627
- api.on("reInit", onSelect);
628
- api.on("select", onSelect);
629
- return () => {
630
- api?.off("select", onSelect);
631
- };
632
- }, [api, onSelect]);
633
- return /* @__PURE__ */ jsx(
634
- CarouselContext.Provider,
635
- {
636
- value: {
637
- carouselRef,
638
- api,
639
- opts,
640
- orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
641
- scrollPrev,
642
- scrollNext,
643
- canScrollPrev,
644
- canScrollNext
645
- },
646
- children: /* @__PURE__ */ jsx(
647
- "div",
648
- {
649
- onKeyDownCapture: handleKeyDown,
650
- className: cn("relative", className),
651
- role: "region",
652
- "aria-roledescription": "carousel",
653
- "data-slot": "carousel",
654
- ...props,
655
- children
656
- }
657
- )
658
- }
659
- );
660
- }
661
- function CarouselContent({ className, ...props }) {
662
- const { carouselRef, orientation } = useCarousel();
663
- return /* @__PURE__ */ jsx(
664
- "div",
665
- {
666
- ref: carouselRef,
667
- className: "overflow-hidden",
668
- "data-slot": "carousel-content",
669
- children: /* @__PURE__ */ jsx(
670
- "div",
671
- {
672
- className: cn(
673
- "flex",
674
- orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
675
- className
676
- ),
677
- ...props
678
- }
679
- )
680
- }
681
- );
682
- }
683
- function CarouselItem({ className, ...props }) {
684
- const { orientation } = useCarousel();
685
- return /* @__PURE__ */ jsx(
686
- "div",
687
- {
688
- role: "group",
689
- "aria-roledescription": "slide",
690
- "data-slot": "carousel-item",
691
- className: cn(
692
- "min-w-0 shrink-0 grow-0 basis-full",
693
- orientation === "horizontal" ? "pl-4" : "pt-4",
694
- className
695
- ),
696
- ...props
697
- }
698
- );
699
- }
700
- function CarouselPrevious({
701
- className,
702
- variant = "outline",
703
- size = "icon",
704
- ...props
705
- }) {
706
- const { orientation, scrollPrev, canScrollPrev } = useCarousel();
707
- return /* @__PURE__ */ jsxs(
708
- Pressable,
709
- {
710
- "data-slot": "carousel-previous",
711
- variant,
712
- size,
713
- className: cn(
714
- "absolute size-8 rounded-full",
715
- orientation === "horizontal" ? "top-1/2 -left-12 -translate-y-1/2" : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
716
- className
717
- ),
718
- disabled: !canScrollPrev,
719
- onClick: scrollPrev,
720
- asButton: true,
721
- ...props,
722
- children: [
723
- /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/arrow-left" }),
724
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Previous slide" })
725
- ]
726
- }
727
- );
728
- }
729
- function CarouselNext({
730
- className,
731
- variant = "outline",
732
- size = "icon",
733
- ...props
734
- }) {
735
- const { orientation, scrollNext, canScrollNext } = useCarousel();
736
- return /* @__PURE__ */ jsxs(
737
- Pressable,
738
- {
739
- "data-slot": "carousel-next",
740
- variant,
741
- size,
742
- className: cn(
743
- "absolute size-8 rounded-full",
744
- orientation === "horizontal" ? "top-1/2 -right-12 -translate-y-1/2" : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
745
- className
746
- ),
747
- disabled: !canScrollNext,
748
- onClick: scrollNext,
749
- asButton: true,
750
- ...props,
751
- children: [
752
- /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/arrow-right" }),
753
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Next slide" })
754
- ]
755
- }
756
- );
598
+ ),
599
+ /* @__PURE__ */ jsx(
600
+ Pressable,
601
+ {
602
+ onClick: onNext,
603
+ disabled: !canScrollNext,
604
+ "aria-label": nextAriaLabel,
605
+ asButton: true,
606
+ className: cn(
607
+ "relative z-40 flex h-10 w-10 items-center justify-center rounded-full disabled:opacity-50",
608
+ buttonClassName
609
+ ),
610
+ children: /* @__PURE__ */ jsx(DynamicIcon, { name: nextIcon, size: iconSize })
611
+ }
612
+ )
613
+ ] });
757
614
  }
758
615
  var maxWidthStyles = {
759
616
  sm: "max-w-screen-sm",
@@ -764,7 +621,7 @@ var maxWidthStyles = {
764
621
  "4xl": "max-w-[1536px]",
765
622
  full: "max-w-full"
766
623
  };
767
- var Container = React4__default.forwardRef(
624
+ var Container = React6__default.forwardRef(
768
625
  ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
769
626
  const Component = as;
770
627
  return /* @__PURE__ */ jsx(
@@ -1069,7 +926,7 @@ var spacingStyles = {
1069
926
  };
1070
927
  var predefinedSpacings = ["none", "sm", "md", "lg", "xl"];
1071
928
  var isPredefinedSpacing = (spacing) => predefinedSpacings.includes(spacing);
1072
- var Section = React4__default.forwardRef(
929
+ var Section = React6__default.forwardRef(
1073
930
  ({
1074
931
  id,
1075
932
  title,
@@ -1157,27 +1014,62 @@ function CarouselFeatureBadge({
1157
1014
  slideLayoutVariant = "square",
1158
1015
  containerMaxWidth = "2xl"
1159
1016
  }) {
1017
+ const [emblaRef, emblaApi] = useEmblaCarousel();
1018
+ const [canScrollPrev, setCanScrollPrev] = React6.useState(false);
1019
+ const [canScrollNext, setCanScrollNext] = React6.useState(false);
1020
+ const scrollPrev = React6.useCallback(() => {
1021
+ emblaApi?.scrollPrev();
1022
+ }, [emblaApi]);
1023
+ const scrollNext = React6.useCallback(() => {
1024
+ emblaApi?.scrollNext();
1025
+ }, [emblaApi]);
1026
+ const onSelect = React6.useCallback(() => {
1027
+ if (!emblaApi) return;
1028
+ setCanScrollPrev(emblaApi.canScrollPrev());
1029
+ setCanScrollNext(emblaApi.canScrollNext());
1030
+ }, [emblaApi]);
1031
+ React6.useEffect(() => {
1032
+ if (!emblaApi) return;
1033
+ onSelect();
1034
+ emblaApi.on("reInit", onSelect);
1035
+ emblaApi.on("select", onSelect);
1036
+ return () => {
1037
+ emblaApi?.off("select", onSelect);
1038
+ };
1039
+ }, [emblaApi, onSelect]);
1160
1040
  const renderCarouselItems = () => {
1161
1041
  if (itemsSlot) return itemsSlot;
1162
1042
  if (!items || items.length === 0) return null;
1163
- return items.map((item, index) => /* @__PURE__ */ jsx(CarouselItem, { className: carouselItemClassName, children: /* @__PURE__ */ jsx(
1043
+ return items.map((item, index) => /* @__PURE__ */ jsx(
1164
1044
  "div",
1165
1045
  {
1046
+ role: "group",
1047
+ "aria-roledescription": "slide",
1166
1048
  className: cn(
1167
- "flex items-center justify-center overflow-hidden rounded-2xl",
1168
- SLIDE_LAYOUT_ASPECT_MAP[slideLayoutVariant]
1049
+ "min-w-0 shrink-0 grow-0 basis-full pl-4",
1050
+ carouselItemClassName
1169
1051
  ),
1170
1052
  children: /* @__PURE__ */ jsx(
1171
- Img,
1053
+ "div",
1172
1054
  {
1173
- src: item.src,
1174
- alt: item.alt,
1175
- className: cn("h-full w-full object-cover", item.className),
1176
- optixFlowConfig
1055
+ className: cn(
1056
+ "flex items-center justify-center overflow-hidden rounded-2xl",
1057
+ SLIDE_LAYOUT_ASPECT_MAP[slideLayoutVariant]
1058
+ ),
1059
+ children: /* @__PURE__ */ jsx(
1060
+ Img,
1061
+ {
1062
+ src: item.src,
1063
+ alt: item.alt,
1064
+ className: cn("h-full w-full object-cover", item.className),
1065
+ optixFlowConfig
1066
+ }
1067
+ )
1177
1068
  }
1178
1069
  )
1179
- }
1180
- ) }, index));
1070
+ },
1071
+ index
1072
+ ));
1181
1073
  };
1182
1074
  return /* @__PURE__ */ jsx(
1183
1075
  Section,
@@ -1220,10 +1112,18 @@ function CarouselFeatureBadge({
1220
1112
  ]
1221
1113
  }
1222
1114
  ),
1223
- /* @__PURE__ */ jsx("div", { className: cn("w-full max-w-full px-6", carouselClassName), children: /* @__PURE__ */ jsxs(Carousel, { children: [
1224
- /* @__PURE__ */ jsx(CarouselContent, { children: renderCarouselItems() }),
1225
- /* @__PURE__ */ jsx(CarouselPrevious, {}),
1226
- /* @__PURE__ */ jsx(CarouselNext, {})
1115
+ /* @__PURE__ */ jsx("div", { className: cn("w-full max-w-full", carouselClassName), children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1116
+ /* @__PURE__ */ jsx("div", { ref: emblaRef, className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "flex -ml-4", children: renderCarouselItems() }) }),
1117
+ /* @__PURE__ */ jsx(
1118
+ CarouselPagination,
1119
+ {
1120
+ onPrevious: scrollPrev,
1121
+ onNext: scrollNext,
1122
+ canScrollPrevious: canScrollPrev,
1123
+ canScrollNext,
1124
+ className: "mt-4"
1125
+ }
1126
+ )
1227
1127
  ] }) })
1228
1128
  ] }) })
1229
1129
  }