overlapping-cards-scroll 0.1.6 → 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.
@@ -19,12 +19,28 @@ import {
19
19
  Text,
20
20
  View
21
21
  } from "react-native";
22
- import { jsx, jsxs } from "react/jsx-runtime";
22
+ import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
23
23
  var clamp = (value, min, max) => Math.min(Math.max(value, min), max);
24
24
  var PAGE_DOT_POSITIONS = /* @__PURE__ */ new Set(["above", "below", "overlay"]);
25
- var TAB_POSITIONS = /* @__PURE__ */ new Set(["above", "below"]);
25
+ var TABS_POSITION_MAP = {
26
+ "top-left": { side: "top", align: "start", orientation: "horizontal" },
27
+ "top-center": { side: "top", align: "center", orientation: "horizontal" },
28
+ "top-right": { side: "top", align: "end", orientation: "horizontal" },
29
+ "bottom-left": { side: "bottom", align: "start", orientation: "horizontal" },
30
+ "bottom-center": { side: "bottom", align: "center", orientation: "horizontal" },
31
+ "bottom-right": { side: "bottom", align: "end", orientation: "horizontal" },
32
+ "left-top": { side: "left", align: "start", orientation: "vertical" },
33
+ "left-center": { side: "left", align: "center", orientation: "vertical" },
34
+ "left-bottom": { side: "left", align: "end", orientation: "vertical" },
35
+ "right-top": { side: "right", align: "start", orientation: "vertical" },
36
+ "right-center": { side: "right", align: "center", orientation: "vertical" },
37
+ "right-bottom": { side: "right", align: "end", orientation: "vertical" },
38
+ "above": { side: "top", align: "center", orientation: "horizontal" },
39
+ "below": { side: "bottom", align: "center", orientation: "horizontal" }
40
+ };
41
+ var DEFAULT_TABS_POSITION = { side: "top", align: "center", orientation: "horizontal" };
42
+ var parseTabsPosition = (value) => value && TABS_POSITION_MAP[value] || DEFAULT_TABS_POSITION;
26
43
  var normalizePageDotsPosition = (value) => PAGE_DOT_POSITIONS.has(value) ? value : "below";
27
- var normalizeTabsPosition = (value) => TAB_POSITIONS.has(value) ? value : "above";
28
44
  var toNumericOffset = (value, fallback = 0) => {
29
45
  if (typeof value === "number" && Number.isFinite(value)) {
30
46
  return value;
@@ -238,7 +254,8 @@ function OverlappingCardsScrollRN(props) {
238
254
  return null;
239
255
  }, [itemsProp]);
240
256
  const cardCount = cards.length;
241
- const resolvedTabsPosition = normalizeTabsPosition(tabsPosition);
257
+ const parsedTabsPosition = parseTabsPosition(tabsPosition);
258
+ const isVerticalTabs = parsedTabsPosition.orientation === "vertical";
242
259
  const showNavigationTabs = showTabs && cardCount > 1 && cardNames !== null;
243
260
  const resolvedPageDotsOffset = toNumericOffset(pageDotsOffset, 10);
244
261
  const resolvedTabsOffset = toNumericOffset(tabsOffset, 10);
@@ -528,16 +545,21 @@ function OverlappingCardsScrollRN(props) {
528
545
  }
529
546
  );
530
547
  };
531
- const renderTabs = (position) => {
532
- if (!showNavigationTabs || resolvedTabsPosition !== position || cardNames === null) {
548
+ const renderTabs = () => {
549
+ if (!showNavigationTabs || cardNames === null) {
533
550
  return null;
534
551
  }
535
- const containerStyle = position === "above" ? [styles.tabsRow, { marginBottom: resolvedTabsOffset }] : [styles.tabsRow, { marginTop: resolvedTabsOffset }];
552
+ const { side, align, orientation } = parsedTabsPosition;
553
+ const isVertical = orientation === "vertical";
554
+ const justifyContent = align === "start" ? "flex-start" : align === "end" ? "flex-end" : "center";
555
+ const baseStyle = isVertical ? styles.tabsColumn : styles.tabsRow;
556
+ const containerStyle = side === "top" ? [baseStyle, { justifyContent, marginBottom: resolvedTabsOffset }] : side === "bottom" ? [baseStyle, { justifyContent, marginTop: resolvedTabsOffset }] : side === "left" ? [baseStyle, { justifyContent, marginRight: resolvedTabsOffset }] : [baseStyle, { justifyContent, marginLeft: resolvedTabsOffset }];
536
557
  return /* @__PURE__ */ jsx(
537
558
  TabsContainerComponent,
538
559
  {
539
- position,
540
- className: `rn-ocs-tabs rn-ocs-tabs--${position}`,
560
+ position: side,
561
+ align,
562
+ className: `rn-ocs-tabs rn-ocs-tabs--${side}`,
541
563
  style: containerStyle,
542
564
  ariaLabel: "Card tabs",
543
565
  cardNames,
@@ -558,7 +580,8 @@ function OverlappingCardsScrollRN(props) {
558
580
  {
559
581
  name,
560
582
  index,
561
- position,
583
+ position: side,
584
+ align,
562
585
  isPrincipal,
563
586
  influence,
564
587
  animate,
@@ -572,121 +595,126 @@ function OverlappingCardsScrollRN(props) {
572
595
  onPress: pressTab,
573
596
  onClick: pressTab
574
597
  },
575
- `rn-ocs-tab-${position}-${index}`
598
+ `rn-ocs-tab-${side}-${index}`
576
599
  );
577
600
  })
578
601
  }
579
602
  );
580
603
  };
581
- return /* @__PURE__ */ jsx(
582
- OverlappingCardsScrollRNControllerContext.Provider,
583
- {
584
- value: controllerContextValue,
585
- children: /* @__PURE__ */ jsxs(View, { style: [styles.shell, style], children: [
586
- renderTabs("above"),
587
- renderPageDots("above"),
588
- /* @__PURE__ */ jsxs(
589
- View,
590
- {
591
- style: [styles.root, { height: resolvedCardHeight }],
592
- onLayout: (event) => {
593
- const width = event.nativeEvent.layout.width || 1;
594
- setViewportWidth(Math.max(1, width));
595
- },
596
- children: [
597
- /* @__PURE__ */ jsx(
598
- Animated.ScrollView,
604
+ const tabsBeforeStage = parsedTabsPosition.side === "top" || parsedTabsPosition.side === "left";
605
+ const stageAndDots = /* @__PURE__ */ jsxs(Fragment2, { children: [
606
+ renderPageDots("above"),
607
+ /* @__PURE__ */ jsxs(
608
+ View,
609
+ {
610
+ style: [styles.root, { height: resolvedCardHeight }],
611
+ onLayout: (event) => {
612
+ const width = event.nativeEvent.layout.width || 1;
613
+ setViewportWidth(Math.max(1, width));
614
+ },
615
+ children: [
616
+ /* @__PURE__ */ jsx(
617
+ Animated.ScrollView,
618
+ {
619
+ ref: scrollRef,
620
+ horizontal: true,
621
+ style: [styles.scrollRegion, { height: resolvedCardHeight }],
622
+ contentContainerStyle: {
623
+ width: layout.trackWidth,
624
+ height: resolvedCardHeight
625
+ },
626
+ onScroll,
627
+ onScrollBeginDrag: cancelFocusTransition,
628
+ onMomentumScrollBegin: cancelFocusTransition,
629
+ scrollEventThrottle: 16,
630
+ showsHorizontalScrollIndicator,
631
+ snapToInterval: shouldSnapToCard ? layout.stepDistance : void 0,
632
+ snapToAlignment: shouldSnapToCard ? "start" : void 0,
633
+ decelerationRate: shouldSnapToCard ? snapDecelerationRate : "normal",
634
+ disableIntervalMomentum: shouldSnapToCard ? snapDisableIntervalMomentum : false,
635
+ children: /* @__PURE__ */ jsx(
636
+ View,
599
637
  {
600
- ref: scrollRef,
601
- horizontal: true,
602
- style: [styles.scrollRegion, { height: resolvedCardHeight }],
603
- contentContainerStyle: {
604
- width: layout.trackWidth,
605
- height: resolvedCardHeight
606
- },
607
- onScroll,
608
- onScrollBeginDrag: cancelFocusTransition,
609
- onMomentumScrollBegin: cancelFocusTransition,
610
- scrollEventThrottle: 16,
611
- showsHorizontalScrollIndicator,
612
- snapToInterval: shouldSnapToCard ? layout.stepDistance : void 0,
613
- snapToAlignment: shouldSnapToCard ? "start" : void 0,
614
- decelerationRate: shouldSnapToCard ? snapDecelerationRate : "normal",
615
- disableIntervalMomentum: shouldSnapToCard ? snapDisableIntervalMomentum : false,
616
- children: /* @__PURE__ */ jsx(
617
- View,
618
- {
619
- style: [
620
- styles.track,
621
- { width: layout.trackWidth, height: resolvedCardHeight }
638
+ style: [
639
+ styles.track,
640
+ { width: layout.trackWidth, height: resolvedCardHeight }
641
+ ],
642
+ children: cards.map((card, index) => {
643
+ var _a;
644
+ const restingRightX = index === 0 ? 0 : (index - 1) * layout.peek + layout.cardWidth;
645
+ const restingLeftX = index * layout.peek;
646
+ const cardXDuringNormalScroll = index === 0 ? 0 : scrollX.interpolate({
647
+ inputRange: index === 1 ? [0, layout.stepDistance] : [
648
+ (index - 1) * layout.stepDistance,
649
+ index * layout.stepDistance
650
+ ],
651
+ outputRange: [restingRightX, restingLeftX],
652
+ extrapolate: "clamp"
653
+ });
654
+ const cardXDuringFocusTransition = focusTransition ? focusTransitionProgress.interpolate({
655
+ inputRange: [0, 1],
656
+ outputRange: [
657
+ resolveCardXAtProgress(
658
+ index,
659
+ focusTransition.fromProgress,
660
+ layout
661
+ ),
662
+ resolveCardXAtProgress(
663
+ index,
664
+ focusTransition.toProgress,
665
+ layout
666
+ )
622
667
  ],
623
- children: cards.map((card, index) => {
624
- var _a;
625
- const restingRightX = index === 0 ? 0 : (index - 1) * layout.peek + layout.cardWidth;
626
- const restingLeftX = index * layout.peek;
627
- const cardXDuringNormalScroll = index === 0 ? 0 : scrollX.interpolate({
628
- inputRange: index === 1 ? [0, layout.stepDistance] : [
629
- (index - 1) * layout.stepDistance,
630
- index * layout.stepDistance
631
- ],
632
- outputRange: [restingRightX, restingLeftX],
633
- extrapolate: "clamp"
634
- });
635
- const cardXDuringFocusTransition = focusTransition ? focusTransitionProgress.interpolate({
636
- inputRange: [0, 1],
637
- outputRange: [
638
- resolveCardXAtProgress(
639
- index,
640
- focusTransition.fromProgress,
641
- layout
642
- ),
643
- resolveCardXAtProgress(
644
- index,
645
- focusTransition.toProgress,
646
- layout
647
- )
648
- ],
649
- extrapolate: "clamp"
650
- }) : null;
651
- const animatedCardX = cardXDuringFocusTransition != null ? cardXDuringFocusTransition : cardXDuringNormalScroll;
652
- return /* @__PURE__ */ jsx(
653
- Animated.View,
668
+ extrapolate: "clamp"
669
+ }) : null;
670
+ const animatedCardX = cardXDuringFocusTransition != null ? cardXDuringFocusTransition : cardXDuringNormalScroll;
671
+ return /* @__PURE__ */ jsx(
672
+ Animated.View,
673
+ {
674
+ pointerEvents: "box-none",
675
+ style: [
676
+ styles.card,
654
677
  {
655
- pointerEvents: "box-none",
656
- style: [
657
- styles.card,
678
+ width: layout.cardWidth,
679
+ height: resolvedCardHeight,
680
+ transform: [
658
681
  {
659
- width: layout.cardWidth,
660
- height: resolvedCardHeight,
661
- transform: [
662
- {
663
- translateX: Animated.add(scrollX, animatedCardX)
664
- }
665
- ]
666
- },
667
- cardContainerStyle
668
- ],
669
- children: /* @__PURE__ */ jsx(View, { pointerEvents: "auto", style: styles.cardContent, children: /* @__PURE__ */ jsx(
670
- OverlappingCardsScrollRNCardIndexContext.Provider,
671
- {
672
- value: index,
673
- children: card
682
+ translateX: Animated.add(scrollX, animatedCardX)
674
683
  }
675
- ) })
684
+ ]
676
685
  },
677
- (_a = card.key) != null ? _a : `rn-ocs-card-${index}`
678
- );
679
- })
680
- }
681
- )
686
+ cardContainerStyle
687
+ ],
688
+ children: /* @__PURE__ */ jsx(View, { pointerEvents: "auto", style: styles.cardContent, children: /* @__PURE__ */ jsx(
689
+ OverlappingCardsScrollRNCardIndexContext.Provider,
690
+ {
691
+ value: index,
692
+ children: card
693
+ }
694
+ ) })
695
+ },
696
+ (_a = card.key) != null ? _a : `rn-ocs-card-${index}`
697
+ );
698
+ })
682
699
  }
683
- ),
684
- renderPageDots("overlay")
685
- ]
686
- }
687
- ),
688
- renderPageDots("below"),
689
- renderTabs("below")
700
+ )
701
+ }
702
+ ),
703
+ renderPageDots("overlay")
704
+ ]
705
+ }
706
+ ),
707
+ renderPageDots("below")
708
+ ] });
709
+ const stageContent = isVerticalTabs ? /* @__PURE__ */ jsx(View, { style: styles.mainColumn, children: stageAndDots }) : stageAndDots;
710
+ return /* @__PURE__ */ jsx(
711
+ OverlappingCardsScrollRNControllerContext.Provider,
712
+ {
713
+ value: controllerContextValue,
714
+ children: /* @__PURE__ */ jsxs(View, { style: [isVerticalTabs ? styles.shellRow : styles.shell, style], children: [
715
+ tabsBeforeStage ? renderTabs() : null,
716
+ stageContent,
717
+ !tabsBeforeStage ? renderTabs() : null
690
718
  ] })
691
719
  }
692
720
  );
@@ -696,6 +724,15 @@ var styles = StyleSheet.create({
696
724
  width: "100%",
697
725
  minWidth: 0
698
726
  },
727
+ shellRow: {
728
+ width: "100%",
729
+ minWidth: 0,
730
+ flexDirection: "row"
731
+ },
732
+ mainColumn: {
733
+ flex: 1,
734
+ minWidth: 0
735
+ },
699
736
  root: {
700
737
  width: "100%",
701
738
  minWidth: 0,
@@ -751,6 +788,12 @@ var styles = StyleSheet.create({
751
788
  flexWrap: "wrap",
752
789
  zIndex: 6
753
790
  },
791
+ tabsColumn: {
792
+ flexDirection: "column",
793
+ alignItems: "center",
794
+ justifyContent: "center",
795
+ zIndex: 6
796
+ },
754
797
  tab: {
755
798
  borderRadius: 999,
756
799
  borderWidth: 1,
package/dist/styles.css CHANGED
@@ -189,3 +189,34 @@
189
189
  outline: 2px solid rgba(16, 57, 93, 0.52);
190
190
  outline-offset: 2px;
191
191
  }
192
+ .overlapping-cards-scroll--vertical-tabs {
193
+ flex-direction: row;
194
+ }
195
+ .ocs-main-column {
196
+ display: flex;
197
+ flex-direction: column;
198
+ flex: 1;
199
+ min-width: 0;
200
+ min-height: 0;
201
+ }
202
+ .ocs-tabs--vertical {
203
+ flex-direction: column;
204
+ width: auto;
205
+ overflow-x: visible;
206
+ overflow-y: auto;
207
+ }
208
+ .ocs-tabs--align-start {
209
+ justify-content: flex-start;
210
+ }
211
+ .ocs-tabs--align-center {
212
+ justify-content: center;
213
+ }
214
+ .ocs-tabs--align-end {
215
+ justify-content: flex-end;
216
+ }
217
+ .ocs-tabs--vertical .ocs-tab:hover {
218
+ transform: translateX(1px);
219
+ }
220
+ .ocs-tabs--left .ocs-tab:hover {
221
+ transform: translateX(-1px);
222
+ }
@@ -6,10 +6,14 @@ export interface CardItem {
6
6
  id: string | number;
7
7
  jsx: ReactElement;
8
8
  }
9
+ export type TabsPositionSide = "top" | "bottom" | "left" | "right";
10
+ export type TabsPositionAlign = "start" | "center" | "end";
11
+ export type TabsPosition = "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right" | "left-top" | "left-center" | "left-bottom" | "right-top" | "right-center" | "right-bottom" | "above" | "below";
9
12
  export interface OverlappingCardsScrollTabProps {
10
13
  name: string;
11
14
  index: number;
12
- position: "above" | "below";
15
+ position: TabsPositionSide;
16
+ align: TabsPositionAlign;
13
17
  isPrincipal: boolean;
14
18
  influence: number;
15
19
  animate: {
@@ -23,7 +27,8 @@ export interface OverlappingCardsScrollTabProps {
23
27
  }
24
28
  export interface OverlappingCardsScrollTabsContainerProps {
25
29
  children: ReactNode;
26
- position: "above" | "below";
30
+ position: TabsPositionSide;
31
+ align: TabsPositionAlign;
27
32
  className: string;
28
33
  style: CSSProperties;
29
34
  ariaLabel: string;
@@ -51,7 +56,7 @@ type SharedProps = {
51
56
  focusTransitionDuration?: number;
52
57
  ariaLabel?: string;
53
58
  showTabs?: boolean;
54
- tabsPosition?: "above" | "below";
59
+ tabsPosition?: TabsPosition;
55
60
  tabsOffset?: number | string;
56
61
  tabsBehavior?: "smooth" | "auto";
57
62
  tabsClassName?: string;
@@ -1,2 +1,2 @@
1
1
  export { OverlappingCardsScroll, OverlappingCardsScrollFocusTrigger, } from "./OverlappingCardsScroll";
2
- export type { CardItem, OverlappingCardsScrollTabProps, OverlappingCardsScrollTabsContainerProps, } from "./OverlappingCardsScroll";
2
+ export type { CardItem, TabsPosition, TabsPositionSide, TabsPositionAlign, OverlappingCardsScrollTabProps, OverlappingCardsScrollTabsContainerProps, } from "./OverlappingCardsScroll";
@@ -1,4 +1,4 @@
1
1
  import type { OverlappingCardsScrollRNFocusTriggerProps, OverlappingCardsScrollRNProps } from "./OverlappingCardsScrollRN.types";
2
- export type { OverlappingCardsScrollRNFocusTransitionMode, OverlappingCardsScrollRNFocusTriggerBehavior, OverlappingCardsScrollRNFocusTriggerProps, OverlappingCardsScrollRNItem, OverlappingCardsScrollRNPageDotsPosition, OverlappingCardsScrollRNProps, OverlappingCardsScrollRNSnapDecelerationRate, OverlappingCardsScrollRNTabsContainerProps, OverlappingCardsScrollRNTabProps, OverlappingCardsScrollRNTabsPosition, } from "./OverlappingCardsScrollRN.types";
2
+ export type { OverlappingCardsScrollRNFocusTransitionMode, OverlappingCardsScrollRNFocusTriggerBehavior, OverlappingCardsScrollRNFocusTriggerProps, OverlappingCardsScrollRNItem, OverlappingCardsScrollRNPageDotsPosition, OverlappingCardsScrollRNProps, OverlappingCardsScrollRNSnapDecelerationRate, OverlappingCardsScrollRNTabsAlign, OverlappingCardsScrollRNTabsContainerProps, OverlappingCardsScrollRNTabProps, OverlappingCardsScrollRNTabsPosition, } from "./OverlappingCardsScrollRN.types";
3
3
  export declare function OverlappingCardsScrollRNFocusTrigger({ children, style, textStyle, behavior, transitionMode, disabled, accessibilityLabel, testID, onPress, onClick, ...pressableProps }: OverlappingCardsScrollRNFocusTriggerProps): import("react/jsx-runtime").JSX.Element;
4
4
  export declare function OverlappingCardsScrollRN(props: OverlappingCardsScrollRNProps): import("react/jsx-runtime").JSX.Element;
@@ -8,11 +8,13 @@ export type OverlappingCardsScrollRNFocusTriggerBehavior = OverlappingCardsScrol
8
8
  export type OverlappingCardsScrollRNFocusTransitionMode = NonNullable<OverlappingCardsScrollWebFocusTriggerProps["transitionMode"]>;
9
9
  export type OverlappingCardsScrollRNSnapDecelerationRate = "normal" | "fast" | number;
10
10
  export type OverlappingCardsScrollRNItem = OverlappingCardsScrollWebCardItem;
11
- export type OverlappingCardsScrollRNTabsPosition = "above" | "below";
11
+ export type OverlappingCardsScrollRNTabsPosition = "top" | "bottom" | "left" | "right";
12
+ export type OverlappingCardsScrollRNTabsAlign = "start" | "center" | "end";
12
13
  export interface OverlappingCardsScrollRNTabProps {
13
14
  name: string;
14
15
  index: number;
15
16
  position: OverlappingCardsScrollRNTabsPosition;
17
+ align: OverlappingCardsScrollRNTabsAlign;
16
18
  isPrincipal: boolean;
17
19
  influence: number;
18
20
  animate: {
@@ -33,6 +35,7 @@ export interface OverlappingCardsScrollRNTabProps {
33
35
  export interface OverlappingCardsScrollRNTabsContainerProps {
34
36
  children: ReactNode;
35
37
  position: OverlappingCardsScrollRNTabsPosition;
38
+ align: OverlappingCardsScrollRNTabsAlign;
36
39
  className: string;
37
40
  style: StyleProp<ViewStyle>;
38
41
  ariaLabel: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlapping-cards-scroll",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Overlapping cards scroll component for React, React Native, and React Native Web.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -213,3 +213,42 @@
213
213
  outline: 2px solid rgba(16, 57, 93, 0.52);
214
214
  outline-offset: 2px;
215
215
  }
216
+
217
+ .overlapping-cards-scroll--vertical-tabs {
218
+ flex-direction: row;
219
+ }
220
+
221
+ .ocs-main-column {
222
+ display: flex;
223
+ flex-direction: column;
224
+ flex: 1;
225
+ min-width: 0;
226
+ min-height: 0;
227
+ }
228
+
229
+ .ocs-tabs--vertical {
230
+ flex-direction: column;
231
+ width: auto;
232
+ overflow-x: visible;
233
+ overflow-y: auto;
234
+ }
235
+
236
+ .ocs-tabs--align-start {
237
+ justify-content: flex-start;
238
+ }
239
+
240
+ .ocs-tabs--align-center {
241
+ justify-content: center;
242
+ }
243
+
244
+ .ocs-tabs--align-end {
245
+ justify-content: flex-end;
246
+ }
247
+
248
+ .ocs-tabs--vertical .ocs-tab:hover {
249
+ transform: translateX(1px);
250
+ }
251
+
252
+ .ocs-tabs--left .ocs-tab:hover {
253
+ transform: translateX(-1px);
254
+ }