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.
- package/dist/index.cjs +207 -167
- package/dist/index.js +208 -168
- package/dist/react-native-web.cjs +207 -167
- package/dist/react-native-web.js +208 -168
- package/dist/react-native.cjs +155 -112
- package/dist/react-native.js +156 -113
- package/dist/styles.css +31 -0
- package/dist/types/lib/OverlappingCardsScroll.d.ts +8 -3
- package/dist/types/lib/index.d.ts +1 -1
- package/dist/types/rn/OverlappingCardsScrollRN.native.d.ts +1 -1
- package/dist/types/rn/OverlappingCardsScrollRN.types.d.ts +4 -1
- package/package.json +1 -1
- package/src/lib/OverlappingCardsScroll.css +39 -0
- package/src/lib/OverlappingCardsScroll.tsx +243 -170
- package/src/lib/index.ts +3 -0
- package/src/rn/OverlappingCardsScrollRN.native.tsx +203 -136
- package/src/rn/OverlappingCardsScrollRN.types.ts +5 -1
|
@@ -27,6 +27,7 @@ import type {
|
|
|
27
27
|
OverlappingCardsScrollRNTabsContainerProps,
|
|
28
28
|
OverlappingCardsScrollRNTabProps,
|
|
29
29
|
OverlappingCardsScrollRNTabsPosition,
|
|
30
|
+
OverlappingCardsScrollRNTabsAlign,
|
|
30
31
|
} from "./OverlappingCardsScrollRN.types";
|
|
31
32
|
|
|
32
33
|
export type {
|
|
@@ -37,6 +38,7 @@ export type {
|
|
|
37
38
|
OverlappingCardsScrollRNPageDotsPosition,
|
|
38
39
|
OverlappingCardsScrollRNProps,
|
|
39
40
|
OverlappingCardsScrollRNSnapDecelerationRate,
|
|
41
|
+
OverlappingCardsScrollRNTabsAlign,
|
|
40
42
|
OverlappingCardsScrollRNTabsContainerProps,
|
|
41
43
|
OverlappingCardsScrollRNTabProps,
|
|
42
44
|
OverlappingCardsScrollRNTabsPosition,
|
|
@@ -44,14 +46,37 @@ export type {
|
|
|
44
46
|
|
|
45
47
|
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
46
48
|
const PAGE_DOT_POSITIONS = new Set(["above", "below", "overlay"]);
|
|
47
|
-
|
|
49
|
+
interface ParsedTabsPosition {
|
|
50
|
+
side: "top" | "bottom" | "left" | "right";
|
|
51
|
+
align: "start" | "center" | "end";
|
|
52
|
+
orientation: "horizontal" | "vertical";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const TABS_POSITION_MAP: Record<string, ParsedTabsPosition> = {
|
|
56
|
+
"top-left": { side: "top", align: "start", orientation: "horizontal" },
|
|
57
|
+
"top-center": { side: "top", align: "center", orientation: "horizontal" },
|
|
58
|
+
"top-right": { side: "top", align: "end", orientation: "horizontal" },
|
|
59
|
+
"bottom-left": { side: "bottom", align: "start", orientation: "horizontal" },
|
|
60
|
+
"bottom-center": { side: "bottom", align: "center", orientation: "horizontal" },
|
|
61
|
+
"bottom-right": { side: "bottom", align: "end", orientation: "horizontal" },
|
|
62
|
+
"left-top": { side: "left", align: "start", orientation: "vertical" },
|
|
63
|
+
"left-center": { side: "left", align: "center", orientation: "vertical" },
|
|
64
|
+
"left-bottom": { side: "left", align: "end", orientation: "vertical" },
|
|
65
|
+
"right-top": { side: "right", align: "start", orientation: "vertical" },
|
|
66
|
+
"right-center": { side: "right", align: "center", orientation: "vertical" },
|
|
67
|
+
"right-bottom": { side: "right", align: "end", orientation: "vertical" },
|
|
68
|
+
"above": { side: "top", align: "center", orientation: "horizontal" },
|
|
69
|
+
"below": { side: "bottom", align: "center", orientation: "horizontal" },
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const DEFAULT_TABS_POSITION: ParsedTabsPosition = { side: "top", align: "center", orientation: "horizontal" };
|
|
73
|
+
|
|
74
|
+
const parseTabsPosition = (value: string | undefined): ParsedTabsPosition =>
|
|
75
|
+
(value && TABS_POSITION_MAP[value]) || DEFAULT_TABS_POSITION;
|
|
48
76
|
|
|
49
77
|
const normalizePageDotsPosition = (value) =>
|
|
50
78
|
PAGE_DOT_POSITIONS.has(value) ? value : "below";
|
|
51
79
|
|
|
52
|
-
const normalizeTabsPosition = (value) =>
|
|
53
|
-
TAB_POSITIONS.has(value) ? value : "above";
|
|
54
|
-
|
|
55
80
|
const toNumericOffset = (value, fallback = 0) => {
|
|
56
81
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
57
82
|
return value;
|
|
@@ -319,7 +344,8 @@ export function OverlappingCardsScrollRN(props: OverlappingCardsScrollRNProps) {
|
|
|
319
344
|
}, [itemsProp]);
|
|
320
345
|
|
|
321
346
|
const cardCount = cards.length;
|
|
322
|
-
const
|
|
347
|
+
const parsedTabsPosition = parseTabsPosition(tabsPosition);
|
|
348
|
+
const isVerticalTabs = parsedTabsPosition.orientation === "vertical";
|
|
323
349
|
const showNavigationTabs = showTabs && cardCount > 1 && cardNames !== null;
|
|
324
350
|
const resolvedPageDotsOffset = toNumericOffset(pageDotsOffset, 10);
|
|
325
351
|
const resolvedTabsOffset = toNumericOffset(tabsOffset, 10);
|
|
@@ -677,24 +703,33 @@ export function OverlappingCardsScrollRN(props: OverlappingCardsScrollRNProps) {
|
|
|
677
703
|
);
|
|
678
704
|
};
|
|
679
705
|
|
|
680
|
-
const renderTabs = (
|
|
681
|
-
if (
|
|
682
|
-
!showNavigationTabs ||
|
|
683
|
-
resolvedTabsPosition !== position ||
|
|
684
|
-
cardNames === null
|
|
685
|
-
) {
|
|
706
|
+
const renderTabs = () => {
|
|
707
|
+
if (!showNavigationTabs || cardNames === null) {
|
|
686
708
|
return null;
|
|
687
709
|
}
|
|
688
710
|
|
|
711
|
+
const { side, align, orientation } = parsedTabsPosition;
|
|
712
|
+
const isVertical = orientation === "vertical";
|
|
713
|
+
|
|
714
|
+
const justifyContent: "flex-start" | "flex-end" | "center" =
|
|
715
|
+
align === "start" ? "flex-start" : align === "end" ? "flex-end" : "center";
|
|
716
|
+
|
|
717
|
+
const baseStyle = isVertical ? styles.tabsColumn : styles.tabsRow;
|
|
718
|
+
|
|
689
719
|
const containerStyle =
|
|
690
|
-
|
|
691
|
-
? [
|
|
692
|
-
:
|
|
720
|
+
side === "top"
|
|
721
|
+
? [baseStyle, { justifyContent, marginBottom: resolvedTabsOffset }]
|
|
722
|
+
: side === "bottom"
|
|
723
|
+
? [baseStyle, { justifyContent, marginTop: resolvedTabsOffset }]
|
|
724
|
+
: side === "left"
|
|
725
|
+
? [baseStyle, { justifyContent, marginRight: resolvedTabsOffset }]
|
|
726
|
+
: [baseStyle, { justifyContent, marginLeft: resolvedTabsOffset }];
|
|
693
727
|
|
|
694
728
|
return (
|
|
695
729
|
<TabsContainerComponent
|
|
696
|
-
position={
|
|
697
|
-
|
|
730
|
+
position={side}
|
|
731
|
+
align={align}
|
|
732
|
+
className={`rn-ocs-tabs rn-ocs-tabs--${side}`}
|
|
698
733
|
style={containerStyle}
|
|
699
734
|
ariaLabel="Card tabs"
|
|
700
735
|
cardNames={cardNames}
|
|
@@ -715,10 +750,11 @@ export function OverlappingCardsScrollRN(props: OverlappingCardsScrollRNProps) {
|
|
|
715
750
|
|
|
716
751
|
return (
|
|
717
752
|
<TabsComponent
|
|
718
|
-
key={`rn-ocs-tab-${
|
|
753
|
+
key={`rn-ocs-tab-${side}-${index}`}
|
|
719
754
|
name={name}
|
|
720
755
|
index={index}
|
|
721
|
-
position={
|
|
756
|
+
position={side}
|
|
757
|
+
align={align}
|
|
722
758
|
isPrincipal={isPrincipal}
|
|
723
759
|
influence={influence}
|
|
724
760
|
animate={animate}
|
|
@@ -740,128 +776,144 @@ export function OverlappingCardsScrollRN(props: OverlappingCardsScrollRNProps) {
|
|
|
740
776
|
);
|
|
741
777
|
};
|
|
742
778
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
779
|
+
const tabsBeforeStage =
|
|
780
|
+
parsedTabsPosition.side === "top" || parsedTabsPosition.side === "left";
|
|
781
|
+
|
|
782
|
+
// The page dots + card area content (reused for both layouts)
|
|
783
|
+
const stageAndDots = (
|
|
784
|
+
<>
|
|
785
|
+
{renderPageDots("above")}
|
|
786
|
+
<View
|
|
787
|
+
style={[styles.root, { height: resolvedCardHeight }]}
|
|
788
|
+
onLayout={(event) => {
|
|
789
|
+
const width = event.nativeEvent.layout.width || 1;
|
|
790
|
+
setViewportWidth(Math.max(1, width));
|
|
791
|
+
}}
|
|
792
|
+
>
|
|
793
|
+
<Animated.ScrollView
|
|
794
|
+
ref={scrollRef}
|
|
795
|
+
horizontal
|
|
796
|
+
style={[styles.scrollRegion, { height: resolvedCardHeight }]}
|
|
797
|
+
contentContainerStyle={{
|
|
798
|
+
width: layout.trackWidth,
|
|
799
|
+
height: resolvedCardHeight,
|
|
755
800
|
}}
|
|
801
|
+
onScroll={onScroll}
|
|
802
|
+
onScrollBeginDrag={cancelFocusTransition}
|
|
803
|
+
onMomentumScrollBegin={cancelFocusTransition}
|
|
804
|
+
scrollEventThrottle={16}
|
|
805
|
+
showsHorizontalScrollIndicator={showsHorizontalScrollIndicator}
|
|
806
|
+
snapToInterval={shouldSnapToCard ? layout.stepDistance : undefined}
|
|
807
|
+
snapToAlignment={shouldSnapToCard ? "start" : undefined}
|
|
808
|
+
decelerationRate={
|
|
809
|
+
shouldSnapToCard
|
|
810
|
+
? (snapDecelerationRate as number | "normal" | "fast")
|
|
811
|
+
: "normal"
|
|
812
|
+
}
|
|
813
|
+
disableIntervalMomentum={
|
|
814
|
+
shouldSnapToCard ? snapDisableIntervalMomentum : false
|
|
815
|
+
}
|
|
756
816
|
>
|
|
757
|
-
<
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
width: layout.trackWidth,
|
|
763
|
-
height: resolvedCardHeight,
|
|
764
|
-
}}
|
|
765
|
-
onScroll={onScroll}
|
|
766
|
-
onScrollBeginDrag={cancelFocusTransition}
|
|
767
|
-
onMomentumScrollBegin={cancelFocusTransition}
|
|
768
|
-
scrollEventThrottle={16}
|
|
769
|
-
showsHorizontalScrollIndicator={showsHorizontalScrollIndicator}
|
|
770
|
-
snapToInterval={shouldSnapToCard ? layout.stepDistance : undefined}
|
|
771
|
-
snapToAlignment={shouldSnapToCard ? "start" : undefined}
|
|
772
|
-
decelerationRate={
|
|
773
|
-
shouldSnapToCard
|
|
774
|
-
? (snapDecelerationRate as number | "normal" | "fast")
|
|
775
|
-
: "normal"
|
|
776
|
-
}
|
|
777
|
-
disableIntervalMomentum={
|
|
778
|
-
shouldSnapToCard ? snapDisableIntervalMomentum : false
|
|
779
|
-
}
|
|
817
|
+
<View
|
|
818
|
+
style={[
|
|
819
|
+
styles.track,
|
|
820
|
+
{ width: layout.trackWidth, height: resolvedCardHeight },
|
|
821
|
+
]}
|
|
780
822
|
>
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
? [0, layout.stepDistance]
|
|
801
|
-
: [
|
|
802
|
-
(index - 1) * layout.stepDistance,
|
|
803
|
-
index * layout.stepDistance,
|
|
804
|
-
],
|
|
805
|
-
outputRange: [restingRightX, restingLeftX],
|
|
806
|
-
extrapolate: "clamp",
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
const cardXDuringFocusTransition = focusTransition
|
|
810
|
-
? focusTransitionProgress.interpolate({
|
|
811
|
-
inputRange: [0, 1],
|
|
812
|
-
outputRange: [
|
|
813
|
-
resolveCardXAtProgress(
|
|
814
|
-
index,
|
|
815
|
-
focusTransition.fromProgress,
|
|
816
|
-
layout,
|
|
817
|
-
),
|
|
818
|
-
resolveCardXAtProgress(
|
|
819
|
-
index,
|
|
820
|
-
focusTransition.toProgress,
|
|
821
|
-
layout,
|
|
822
|
-
),
|
|
823
|
-
],
|
|
823
|
+
{cards.map((card, index) => {
|
|
824
|
+
const restingRightX =
|
|
825
|
+
index === 0
|
|
826
|
+
? 0
|
|
827
|
+
: (index - 1) * layout.peek + layout.cardWidth;
|
|
828
|
+
const restingLeftX = index * layout.peek;
|
|
829
|
+
|
|
830
|
+
const cardXDuringNormalScroll =
|
|
831
|
+
index === 0
|
|
832
|
+
? 0
|
|
833
|
+
: scrollX.interpolate({
|
|
834
|
+
inputRange:
|
|
835
|
+
index === 1
|
|
836
|
+
? [0, layout.stepDistance]
|
|
837
|
+
: [
|
|
838
|
+
(index - 1) * layout.stepDistance,
|
|
839
|
+
index * layout.stepDistance,
|
|
840
|
+
],
|
|
841
|
+
outputRange: [restingRightX, restingLeftX],
|
|
824
842
|
extrapolate: "clamp",
|
|
825
|
-
})
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
const cardXDuringFocusTransition = focusTransition
|
|
846
|
+
? focusTransitionProgress.interpolate({
|
|
847
|
+
inputRange: [0, 1],
|
|
848
|
+
outputRange: [
|
|
849
|
+
resolveCardXAtProgress(
|
|
850
|
+
index,
|
|
851
|
+
focusTransition.fromProgress,
|
|
852
|
+
layout,
|
|
853
|
+
),
|
|
854
|
+
resolveCardXAtProgress(
|
|
855
|
+
index,
|
|
856
|
+
focusTransition.toProgress,
|
|
857
|
+
layout,
|
|
858
|
+
),
|
|
859
|
+
],
|
|
860
|
+
extrapolate: "clamp",
|
|
861
|
+
})
|
|
862
|
+
: null;
|
|
863
|
+
|
|
864
|
+
const animatedCardX =
|
|
865
|
+
cardXDuringFocusTransition ?? cardXDuringNormalScroll;
|
|
866
|
+
|
|
867
|
+
return (
|
|
868
|
+
<Animated.View
|
|
869
|
+
key={card.key ?? `rn-ocs-card-${index}`}
|
|
870
|
+
pointerEvents="box-none"
|
|
871
|
+
style={[
|
|
872
|
+
styles.card,
|
|
873
|
+
{
|
|
874
|
+
width: layout.cardWidth,
|
|
875
|
+
height: resolvedCardHeight,
|
|
876
|
+
transform: [
|
|
877
|
+
{
|
|
878
|
+
translateX: Animated.add(scrollX, animatedCardX),
|
|
879
|
+
},
|
|
880
|
+
],
|
|
881
|
+
},
|
|
882
|
+
cardContainerStyle,
|
|
883
|
+
]}
|
|
884
|
+
>
|
|
885
|
+
<View pointerEvents="auto" style={styles.cardContent}>
|
|
886
|
+
<OverlappingCardsScrollRNCardIndexContext.Provider
|
|
887
|
+
value={index}
|
|
888
|
+
>
|
|
889
|
+
{card}
|
|
890
|
+
</OverlappingCardsScrollRNCardIndexContext.Provider>
|
|
891
|
+
</View>
|
|
892
|
+
</Animated.View>
|
|
893
|
+
);
|
|
894
|
+
})}
|
|
895
|
+
</View>
|
|
896
|
+
</Animated.ScrollView>
|
|
897
|
+
{renderPageDots("overlay")}
|
|
898
|
+
</View>
|
|
899
|
+
{renderPageDots("below")}
|
|
900
|
+
</>
|
|
901
|
+
);
|
|
902
|
+
|
|
903
|
+
const stageContent = isVerticalTabs ? (
|
|
904
|
+
<View style={styles.mainColumn}>{stageAndDots}</View>
|
|
905
|
+
) : (
|
|
906
|
+
stageAndDots
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
return (
|
|
910
|
+
<OverlappingCardsScrollRNControllerContext.Provider
|
|
911
|
+
value={controllerContextValue}
|
|
912
|
+
>
|
|
913
|
+
<View style={[isVerticalTabs ? styles.shellRow : styles.shell, style]}>
|
|
914
|
+
{tabsBeforeStage ? renderTabs() : null}
|
|
915
|
+
{stageContent}
|
|
916
|
+
{!tabsBeforeStage ? renderTabs() : null}
|
|
865
917
|
</View>
|
|
866
918
|
</OverlappingCardsScrollRNControllerContext.Provider>
|
|
867
919
|
);
|
|
@@ -872,6 +924,15 @@ const styles = StyleSheet.create({
|
|
|
872
924
|
width: "100%",
|
|
873
925
|
minWidth: 0,
|
|
874
926
|
},
|
|
927
|
+
shellRow: {
|
|
928
|
+
width: "100%",
|
|
929
|
+
minWidth: 0,
|
|
930
|
+
flexDirection: "row",
|
|
931
|
+
},
|
|
932
|
+
mainColumn: {
|
|
933
|
+
flex: 1,
|
|
934
|
+
minWidth: 0,
|
|
935
|
+
},
|
|
875
936
|
root: {
|
|
876
937
|
width: "100%",
|
|
877
938
|
minWidth: 0,
|
|
@@ -927,6 +988,12 @@ const styles = StyleSheet.create({
|
|
|
927
988
|
flexWrap: "wrap",
|
|
928
989
|
zIndex: 6,
|
|
929
990
|
},
|
|
991
|
+
tabsColumn: {
|
|
992
|
+
flexDirection: "column",
|
|
993
|
+
alignItems: "center",
|
|
994
|
+
justifyContent: "center",
|
|
995
|
+
zIndex: 6,
|
|
996
|
+
},
|
|
930
997
|
tab: {
|
|
931
998
|
borderRadius: 999,
|
|
932
999
|
borderWidth: 1,
|
|
@@ -37,12 +37,15 @@ export type OverlappingCardsScrollRNSnapDecelerationRate =
|
|
|
37
37
|
|
|
38
38
|
export type OverlappingCardsScrollRNItem = OverlappingCardsScrollWebCardItem;
|
|
39
39
|
|
|
40
|
-
export type OverlappingCardsScrollRNTabsPosition = "
|
|
40
|
+
export type OverlappingCardsScrollRNTabsPosition = "top" | "bottom" | "left" | "right";
|
|
41
|
+
|
|
42
|
+
export type OverlappingCardsScrollRNTabsAlign = "start" | "center" | "end";
|
|
41
43
|
|
|
42
44
|
export interface OverlappingCardsScrollRNTabProps {
|
|
43
45
|
name: string;
|
|
44
46
|
index: number;
|
|
45
47
|
position: OverlappingCardsScrollRNTabsPosition;
|
|
48
|
+
align: OverlappingCardsScrollRNTabsAlign;
|
|
46
49
|
isPrincipal: boolean;
|
|
47
50
|
influence: number;
|
|
48
51
|
animate: {
|
|
@@ -64,6 +67,7 @@ export interface OverlappingCardsScrollRNTabProps {
|
|
|
64
67
|
export interface OverlappingCardsScrollRNTabsContainerProps {
|
|
65
68
|
children: ReactNode;
|
|
66
69
|
position: OverlappingCardsScrollRNTabsPosition;
|
|
70
|
+
align: OverlappingCardsScrollRNTabsAlign;
|
|
67
71
|
className: string;
|
|
68
72
|
style: StyleProp<ViewStyle>;
|
|
69
73
|
ariaLabel: string;
|