overlapping-cards-scroll 0.1.6 → 0.1.8

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,
@@ -194,6 +210,7 @@ function OverlappingCardsScroll(props) {
194
210
  }, [itemsProp]);
195
211
  const cardCount = cards.length;
196
212
  const containerRef = (0, import_react.useRef)(null);
213
+ const stageRef = (0, import_react.useRef)(null);
197
214
  const scrollRef = (0, import_react.useRef)(null);
198
215
  const touchStateRef = (0, import_react.useRef)(null);
199
216
  const snapTimeoutRef = (0, import_react.useRef)(null);
@@ -220,9 +237,9 @@ function OverlappingCardsScroll(props) {
220
237
  }, [clearFocusTransitionTimeout]);
221
238
  (0, import_react.useEffect)(() => {
222
239
  var _a;
223
- const containerElement = containerRef.current;
240
+ const stageElement = stageRef.current;
224
241
  const scrollElement = scrollRef.current;
225
- if (!containerElement || !scrollElement) {
242
+ if (!stageElement || !scrollElement) {
226
243
  return void 0;
227
244
  }
228
245
  const syncScroll = () => {
@@ -242,8 +259,8 @@ function OverlappingCardsScroll(props) {
242
259
  applyWidth(width);
243
260
  syncScroll();
244
261
  });
245
- resizeObserver.observe(containerElement);
246
- applyWidth((_a = containerElement.getBoundingClientRect().width) != null ? _a : 0);
262
+ resizeObserver.observe(stageElement);
263
+ applyWidth((_a = stageElement.getBoundingClientRect().width) != null ? _a : 0);
247
264
  syncScroll();
248
265
  scrollElement.addEventListener("scroll", syncScroll, { passive: true });
249
266
  return () => {
@@ -555,7 +572,6 @@ function OverlappingCardsScroll(props) {
555
572
  }
556
573
  touchStateRef.current = null;
557
574
  };
558
- const stageRef = (0, import_react.useRef)(null);
559
575
  (0, import_react.useEffect)(() => {
560
576
  const stageElement = stageRef.current;
561
577
  if (!stageElement) {
@@ -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,159 +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
- flexDirection: "column"
709
- },
710
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
711
- OverlappingCardsScrollCardIndexContext.Provider,
712
- {
713
- value: index,
714
- children: card
715
- }
716
- )
717
- }
718
- )
719
- },
720
- (_a = card.key) != null ? _a : `ocs-card-${index}`
721
- );
722
- }) }),
723
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ocs-scroll-region", ref: scrollRef, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
724
- "div",
725
- {
726
- className: "ocs-scroll-spacer",
727
- style: {
728
- width: `${layout.trackWidth}px`
729
- }
730
- }
731
- ) })
732
- ]
733
- }
734
- ),
735
- showNavigationDots && resolvedPageDotsPosition === "overlay" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
736
- "nav",
737
- {
738
- className: pageDotsClassName ? `ocs-page-dots ocs-page-dots--overlay ${pageDotsClassName}` : "ocs-page-dots ocs-page-dots--overlay",
739
- style: { bottom: toCssDimension(pageDotsOffset) },
740
- "aria-label": "Card pages",
741
- children: cards.map((_, index) => {
742
- const influence = clamp(1 - Math.abs(progress - index), 0, 1);
743
- const opacity = 0.25 + influence * 0.75;
744
- const scale = 0.9 + influence * 0.22;
745
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
746
- "button",
747
- {
748
- type: "button",
749
- className: "ocs-page-dot",
750
- "aria-label": `Go to card ${index + 1}`,
751
- "aria-current": influence > 0.98 ? "page" : void 0,
752
- onClick: () => focusCard(index, {
753
- behavior: pageDotsBehavior,
754
- transitionMode: "swoop"
755
- }),
756
- style: { opacity, transform: `scale(${scale})` }
757
- },
758
- `ocs-page-dot-overlay-${index}`
759
- );
760
- })
761
- }
762
- ) : null
763
- ] }),
764
- showNavigationDots && resolvedPageDotsPosition === "below" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
765
- "nav",
766
- {
767
- className: pageDotsClassName ? `ocs-page-dots ocs-page-dots--below ${pageDotsClassName}` : "ocs-page-dots ocs-page-dots--below",
768
- style: { marginTop: toCssDimension(pageDotsOffset) },
769
- "aria-label": "Card pages",
770
- children: cards.map((_, index) => {
771
- const influence = clamp(1 - Math.abs(progress - index), 0, 1);
772
- const opacity = 0.25 + influence * 0.75;
773
- const scale = 0.9 + influence * 0.22;
774
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
775
- "button",
776
- {
777
- type: "button",
778
- className: "ocs-page-dot",
779
- "aria-label": `Go to card ${index + 1}`,
780
- "aria-current": influence > 0.98 ? "page" : void 0,
781
- onClick: () => focusCard(index, {
782
- behavior: pageDotsBehavior,
783
- transitionMode: "swoop"
784
- }),
785
- style: { opacity, transform: `scale(${scale})` }
786
- },
787
- `ocs-page-dot-below-${index}`
788
- );
789
- })
790
- }
791
- ) : null,
792
- resolvedTabsPosition === "below" ? renderTabs("below") : null
830
+ tabsBeforeStage ? renderTabs() : null,
831
+ stageContent,
832
+ !tabsBeforeStage ? renderTabs() : null
793
833
  ]
794
834
  }
795
835
  )