overlapping-cards-scroll 0.1.5 → 0.1.7

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/index.cjs CHANGED
@@ -31,8 +31,24 @@ var clamp = (value, min, max) => Math.min(Math.max(value, min), max);
31
31
  var toCssDimension = (value) => typeof value === "number" ? `${value}px` : value;
32
32
  var PAGE_DOT_POSITIONS = /* @__PURE__ */ new Set(["above", "below", "overlay"]);
33
33
  var normalizePageDotsPosition = (value) => PAGE_DOT_POSITIONS.has(value) ? value : "below";
34
- var TAB_POSITIONS = /* @__PURE__ */ new Set(["above", "below"]);
35
- var normalizeTabsPosition = (value) => TAB_POSITIONS.has(value) ? value : "above";
34
+ var TABS_POSITION_MAP = {
35
+ "top-left": { side: "top", align: "start", orientation: "horizontal" },
36
+ "top-center": { side: "top", align: "center", orientation: "horizontal" },
37
+ "top-right": { side: "top", align: "end", orientation: "horizontal" },
38
+ "bottom-left": { side: "bottom", align: "start", orientation: "horizontal" },
39
+ "bottom-center": { side: "bottom", align: "center", orientation: "horizontal" },
40
+ "bottom-right": { side: "bottom", align: "end", orientation: "horizontal" },
41
+ "left-top": { side: "left", align: "start", orientation: "vertical" },
42
+ "left-center": { side: "left", align: "center", orientation: "vertical" },
43
+ "left-bottom": { side: "left", align: "end", orientation: "vertical" },
44
+ "right-top": { side: "right", align: "start", orientation: "vertical" },
45
+ "right-center": { side: "right", align: "center", orientation: "vertical" },
46
+ "right-bottom": { side: "right", align: "end", orientation: "vertical" },
47
+ "above": { side: "top", align: "center", orientation: "horizontal" },
48
+ "below": { side: "bottom", align: "center", orientation: "horizontal" }
49
+ };
50
+ var DEFAULT_TABS_POSITION = { side: "top", align: "center", orientation: "horizontal" };
51
+ var parseTabsPosition = (value) => value && TABS_POSITION_MAP[value] || DEFAULT_TABS_POSITION;
36
52
  var lastKnownViewportWidth = 1;
37
53
  function DefaultTabsContainerComponent({
38
54
  children,
@@ -566,11 +582,16 @@ function OverlappingCardsScroll(props) {
566
582
  stageElement.removeEventListener("wheel", handleWheel);
567
583
  };
568
584
  }, [handleWheel]);
569
- const containerClassName = className ? `overlapping-cards-scroll ${className}` : "overlapping-cards-scroll";
570
585
  const resolvedPageDotsPosition = normalizePageDotsPosition(pageDotsPosition);
571
586
  const showNavigationDots = showPageDots && cardCount > 1;
572
- const resolvedTabsPosition = normalizeTabsPosition(tabsPosition);
587
+ const parsedTabsPosition = parseTabsPosition(tabsPosition);
588
+ const isVerticalTabs = parsedTabsPosition.orientation === "vertical";
573
589
  const showNavigationTabs = showTabs && cardCount > 1 && cardNames !== null;
590
+ const containerClassName = [
591
+ "overlapping-cards-scroll",
592
+ isVerticalTabs ? "overlapping-cards-scroll--vertical-tabs" : "",
593
+ className
594
+ ].filter(Boolean).join(" ");
574
595
  (0, import_react.useEffect)(() => {
575
596
  if (showTabs && cardNames === null) {
576
597
  console.warn(
@@ -578,17 +599,32 @@ function OverlappingCardsScroll(props) {
578
599
  );
579
600
  }
580
601
  }, [showTabs, cardNames]);
581
- const renderTabs = (position) => {
602
+ const renderTabs = () => {
582
603
  if (!showNavigationTabs || cardNames === null) {
583
604
  return null;
584
605
  }
585
- const containerClassName2 = tabsClassName ? `ocs-tabs ocs-tabs--${position} ${tabsClassName}` : `ocs-tabs ocs-tabs--${position}`;
586
- const containerStyle = position === "above" ? { marginBottom: toCssDimension(tabsOffset) } : { marginTop: toCssDimension(tabsOffset) };
606
+ const { side, align, orientation } = parsedTabsPosition;
607
+ const isVertical = orientation === "vertical";
608
+ const alignClass = align === "start" ? "ocs-tabs--align-start" : align === "end" ? "ocs-tabs--align-end" : "ocs-tabs--align-center";
609
+ const orientationClass = isVertical ? "ocs-tabs--vertical" : "";
610
+ const classNames = [
611
+ "ocs-tabs",
612
+ `ocs-tabs--${side}`,
613
+ alignClass,
614
+ orientationClass,
615
+ tabsClassName
616
+ ].filter(Boolean).join(" ");
617
+ const containerStyle = {};
618
+ if (side === "top") containerStyle.marginBottom = toCssDimension(tabsOffset);
619
+ else if (side === "bottom") containerStyle.marginTop = toCssDimension(tabsOffset);
620
+ else if (side === "left") containerStyle.marginRight = toCssDimension(tabsOffset);
621
+ else if (side === "right") containerStyle.marginLeft = toCssDimension(tabsOffset);
587
622
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
588
623
  TabsContainerComponent,
589
624
  {
590
- position,
591
- className: containerClassName2,
625
+ position: side,
626
+ align,
627
+ className: classNames,
592
628
  style: containerStyle,
593
629
  ariaLabel: "Card tabs",
594
630
  cardNames,
@@ -597,9 +633,7 @@ function OverlappingCardsScroll(props) {
597
633
  children: cardNames.map((name, index) => {
598
634
  const influence = clamp(1 - Math.abs(progress - index), 0, 1);
599
635
  const isPrincipal = influence > 0.98;
600
- const animate = {
601
- opacity: 0.45 + influence * 0.55
602
- };
636
+ const animate = { opacity: 0.45 + influence * 0.55 };
603
637
  const className2 = isPrincipal ? "ocs-tab ocs-tab--active" : "ocs-tab";
604
638
  const style = { opacity: animate.opacity };
605
639
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -607,7 +641,8 @@ function OverlappingCardsScroll(props) {
607
641
  {
608
642
  name,
609
643
  index,
610
- position,
644
+ position: side,
645
+ align,
611
646
  isPrincipal,
612
647
  influence,
613
648
  animate,
@@ -620,12 +655,167 @@ function OverlappingCardsScroll(props) {
620
655
  transitionMode: "swoop"
621
656
  })
622
657
  },
623
- `ocs-tab-${position}-${index}`
658
+ `ocs-tab-${side}-${index}`
624
659
  );
625
660
  })
626
661
  }
627
662
  );
628
663
  };
664
+ const tabsBeforeStage = parsedTabsPosition.side === "top" || parsedTabsPosition.side === "left";
665
+ const stageAndDots = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
666
+ showNavigationDots && resolvedPageDotsPosition === "above" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
667
+ "nav",
668
+ {
669
+ className: pageDotsClassName ? `ocs-page-dots ocs-page-dots--above ${pageDotsClassName}` : "ocs-page-dots ocs-page-dots--above",
670
+ style: { marginBottom: toCssDimension(pageDotsOffset) },
671
+ "aria-label": "Card pages",
672
+ children: cards.map((_, index) => {
673
+ const influence = clamp(1 - Math.abs(progress - index), 0, 1);
674
+ const opacity = 0.25 + influence * 0.75;
675
+ const scale = 0.9 + influence * 0.22;
676
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
677
+ "button",
678
+ {
679
+ type: "button",
680
+ className: "ocs-page-dot",
681
+ "aria-label": `Go to card ${index + 1}`,
682
+ "aria-current": influence > 0.98 ? "page" : void 0,
683
+ onClick: () => focusCard(index, {
684
+ behavior: pageDotsBehavior,
685
+ transitionMode: "swoop"
686
+ }),
687
+ style: { opacity, transform: `scale(${scale})` }
688
+ },
689
+ `ocs-page-dot-above-${index}`
690
+ );
691
+ })
692
+ }
693
+ ) : null,
694
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ocs-stage-frame", children: [
695
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
696
+ "div",
697
+ {
698
+ className: "ocs-stage",
699
+ ref: stageRef,
700
+ style: {
701
+ minHeight: toCssDimension(cardHeight)
702
+ },
703
+ onTouchStart: handleTouchStart,
704
+ onTouchMove: handleTouchMove,
705
+ onTouchEnd: handleTouchEnd,
706
+ onTouchCancel: handleTouchEnd,
707
+ children: [
708
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ocs-track", children: cards.map((card, index) => {
709
+ var _a;
710
+ const cardX = resolveCardX(
711
+ index,
712
+ activeIndex,
713
+ transitionProgress,
714
+ layout
715
+ );
716
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
717
+ "div",
718
+ {
719
+ className: cardContainerClassName ? `${focusTransition ? "ocs-card ocs-card--focus-transition" : "ocs-card"} ${cardContainerClassName}` : focusTransition ? "ocs-card ocs-card--focus-transition" : "ocs-card",
720
+ style: {
721
+ width: `${layout.cardWidth}px`,
722
+ transform: `translate3d(${cardX}px, 0, 0)`,
723
+ transitionDuration: focusTransition ? `${focusTransition.duration}ms` : void 0,
724
+ ...cardContainerStyle,
725
+ pointerEvents: "none"
726
+ },
727
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
728
+ "div",
729
+ {
730
+ style: {
731
+ pointerEvents: "auto",
732
+ display: "flex",
733
+ flexDirection: "column"
734
+ },
735
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
736
+ OverlappingCardsScrollCardIndexContext.Provider,
737
+ {
738
+ value: index,
739
+ children: card
740
+ }
741
+ )
742
+ }
743
+ )
744
+ },
745
+ (_a = card.key) != null ? _a : `ocs-card-${index}`
746
+ );
747
+ }) }),
748
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ocs-scroll-region", ref: scrollRef, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
749
+ "div",
750
+ {
751
+ className: "ocs-scroll-spacer",
752
+ style: {
753
+ width: `${layout.trackWidth}px`
754
+ }
755
+ }
756
+ ) })
757
+ ]
758
+ }
759
+ ),
760
+ showNavigationDots && resolvedPageDotsPosition === "overlay" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
761
+ "nav",
762
+ {
763
+ className: pageDotsClassName ? `ocs-page-dots ocs-page-dots--overlay ${pageDotsClassName}` : "ocs-page-dots ocs-page-dots--overlay",
764
+ style: { bottom: toCssDimension(pageDotsOffset) },
765
+ "aria-label": "Card pages",
766
+ children: cards.map((_, index) => {
767
+ const influence = clamp(1 - Math.abs(progress - index), 0, 1);
768
+ const opacity = 0.25 + influence * 0.75;
769
+ const scale = 0.9 + influence * 0.22;
770
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
771
+ "button",
772
+ {
773
+ type: "button",
774
+ className: "ocs-page-dot",
775
+ "aria-label": `Go to card ${index + 1}`,
776
+ "aria-current": influence > 0.98 ? "page" : void 0,
777
+ onClick: () => focusCard(index, {
778
+ behavior: pageDotsBehavior,
779
+ transitionMode: "swoop"
780
+ }),
781
+ style: { opacity, transform: `scale(${scale})` }
782
+ },
783
+ `ocs-page-dot-overlay-${index}`
784
+ );
785
+ })
786
+ }
787
+ ) : null
788
+ ] }),
789
+ showNavigationDots && resolvedPageDotsPosition === "below" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
790
+ "nav",
791
+ {
792
+ className: pageDotsClassName ? `ocs-page-dots ocs-page-dots--below ${pageDotsClassName}` : "ocs-page-dots ocs-page-dots--below",
793
+ style: { marginTop: toCssDimension(pageDotsOffset) },
794
+ "aria-label": "Card pages",
795
+ children: cards.map((_, index) => {
796
+ const influence = clamp(1 - Math.abs(progress - index), 0, 1);
797
+ const opacity = 0.25 + influence * 0.75;
798
+ const scale = 0.9 + influence * 0.22;
799
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
800
+ "button",
801
+ {
802
+ type: "button",
803
+ className: "ocs-page-dot",
804
+ "aria-label": `Go to card ${index + 1}`,
805
+ "aria-current": influence > 0.98 ? "page" : void 0,
806
+ onClick: () => focusCard(index, {
807
+ behavior: pageDotsBehavior,
808
+ transitionMode: "swoop"
809
+ }),
810
+ style: { opacity, transform: `scale(${scale})` }
811
+ },
812
+ `ocs-page-dot-below-${index}`
813
+ );
814
+ })
815
+ }
816
+ ) : null
817
+ ] });
818
+ const stageContent = isVerticalTabs ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ocs-main-column", children: stageAndDots }) : stageAndDots;
629
819
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
630
820
  OverlappingCardsScrollControllerContext.Provider,
631
821
  {
@@ -637,158 +827,9 @@ function OverlappingCardsScroll(props) {
637
827
  "aria-label": ariaLabel,
638
828
  ref: containerRef,
639
829
  children: [
640
- resolvedTabsPosition === "above" ? renderTabs("above") : null,
641
- showNavigationDots && resolvedPageDotsPosition === "above" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
642
- "nav",
643
- {
644
- className: pageDotsClassName ? `ocs-page-dots ocs-page-dots--above ${pageDotsClassName}` : "ocs-page-dots ocs-page-dots--above",
645
- style: { marginBottom: toCssDimension(pageDotsOffset) },
646
- "aria-label": "Card pages",
647
- children: cards.map((_, index) => {
648
- const influence = clamp(1 - Math.abs(progress - index), 0, 1);
649
- const opacity = 0.25 + influence * 0.75;
650
- const scale = 0.9 + influence * 0.22;
651
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
652
- "button",
653
- {
654
- type: "button",
655
- className: "ocs-page-dot",
656
- "aria-label": `Go to card ${index + 1}`,
657
- "aria-current": influence > 0.98 ? "page" : void 0,
658
- onClick: () => focusCard(index, {
659
- behavior: pageDotsBehavior,
660
- transitionMode: "swoop"
661
- }),
662
- style: { opacity, transform: `scale(${scale})` }
663
- },
664
- `ocs-page-dot-above-${index}`
665
- );
666
- })
667
- }
668
- ) : null,
669
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ocs-stage-frame", children: [
670
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
671
- "div",
672
- {
673
- className: "ocs-stage",
674
- ref: stageRef,
675
- style: {
676
- minHeight: toCssDimension(cardHeight)
677
- },
678
- onTouchStart: handleTouchStart,
679
- onTouchMove: handleTouchMove,
680
- onTouchEnd: handleTouchEnd,
681
- onTouchCancel: handleTouchEnd,
682
- children: [
683
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ocs-track", children: cards.map((card, index) => {
684
- var _a;
685
- const cardX = resolveCardX(
686
- index,
687
- activeIndex,
688
- transitionProgress,
689
- layout
690
- );
691
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
692
- "div",
693
- {
694
- className: cardContainerClassName ? `${focusTransition ? "ocs-card ocs-card--focus-transition" : "ocs-card"} ${cardContainerClassName}` : focusTransition ? "ocs-card ocs-card--focus-transition" : "ocs-card",
695
- style: {
696
- width: `${layout.cardWidth}px`,
697
- transform: `translate3d(${cardX}px, 0, 0)`,
698
- transitionDuration: focusTransition ? `${focusTransition.duration}ms` : void 0,
699
- ...cardContainerStyle,
700
- pointerEvents: "none"
701
- },
702
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
703
- "div",
704
- {
705
- style: {
706
- pointerEvents: "auto",
707
- display: "flex"
708
- },
709
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
710
- OverlappingCardsScrollCardIndexContext.Provider,
711
- {
712
- value: index,
713
- children: card
714
- }
715
- )
716
- }
717
- )
718
- },
719
- (_a = card.key) != null ? _a : `ocs-card-${index}`
720
- );
721
- }) }),
722
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ocs-scroll-region", ref: scrollRef, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
723
- "div",
724
- {
725
- className: "ocs-scroll-spacer",
726
- style: {
727
- width: `${layout.trackWidth}px`
728
- }
729
- }
730
- ) })
731
- ]
732
- }
733
- ),
734
- showNavigationDots && resolvedPageDotsPosition === "overlay" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
735
- "nav",
736
- {
737
- className: pageDotsClassName ? `ocs-page-dots ocs-page-dots--overlay ${pageDotsClassName}` : "ocs-page-dots ocs-page-dots--overlay",
738
- style: { bottom: toCssDimension(pageDotsOffset) },
739
- "aria-label": "Card pages",
740
- children: cards.map((_, index) => {
741
- const influence = clamp(1 - Math.abs(progress - index), 0, 1);
742
- const opacity = 0.25 + influence * 0.75;
743
- const scale = 0.9 + influence * 0.22;
744
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
745
- "button",
746
- {
747
- type: "button",
748
- className: "ocs-page-dot",
749
- "aria-label": `Go to card ${index + 1}`,
750
- "aria-current": influence > 0.98 ? "page" : void 0,
751
- onClick: () => focusCard(index, {
752
- behavior: pageDotsBehavior,
753
- transitionMode: "swoop"
754
- }),
755
- style: { opacity, transform: `scale(${scale})` }
756
- },
757
- `ocs-page-dot-overlay-${index}`
758
- );
759
- })
760
- }
761
- ) : null
762
- ] }),
763
- showNavigationDots && resolvedPageDotsPosition === "below" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
764
- "nav",
765
- {
766
- className: pageDotsClassName ? `ocs-page-dots ocs-page-dots--below ${pageDotsClassName}` : "ocs-page-dots ocs-page-dots--below",
767
- style: { marginTop: toCssDimension(pageDotsOffset) },
768
- "aria-label": "Card pages",
769
- children: cards.map((_, index) => {
770
- const influence = clamp(1 - Math.abs(progress - index), 0, 1);
771
- const opacity = 0.25 + influence * 0.75;
772
- const scale = 0.9 + influence * 0.22;
773
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
774
- "button",
775
- {
776
- type: "button",
777
- className: "ocs-page-dot",
778
- "aria-label": `Go to card ${index + 1}`,
779
- "aria-current": influence > 0.98 ? "page" : void 0,
780
- onClick: () => focusCard(index, {
781
- behavior: pageDotsBehavior,
782
- transitionMode: "swoop"
783
- }),
784
- style: { opacity, transform: `scale(${scale})` }
785
- },
786
- `ocs-page-dot-below-${index}`
787
- );
788
- })
789
- }
790
- ) : null,
791
- resolvedTabsPosition === "below" ? renderTabs("below") : null
830
+ tabsBeforeStage ? renderTabs() : null,
831
+ stageContent,
832
+ !tabsBeforeStage ? renderTabs() : null
792
833
  ]
793
834
  }
794
835
  )