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 +212 -172
- package/dist/index.js +213 -173
- package/dist/react-native-web.cjs +212 -172
- package/dist/react-native-web.js +213 -173
- 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 +248 -176
- package/src/lib/index.ts +3 -0
- package/src/rn/OverlappingCardsScrollRN.native.tsx +203 -136
- package/src/rn/OverlappingCardsScrollRN.types.ts +5 -1
- package/src/rn/RNWebDemo.tsx +45 -0
|
@@ -25,15 +25,24 @@ export interface CardItem {
|
|
|
25
25
|
jsx: ReactElement;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export type TabsPositionSide = "top" | "bottom" | "left" | "right";
|
|
29
|
+
export type TabsPositionAlign = "start" | "center" | "end";
|
|
30
|
+
|
|
31
|
+
export type TabsPosition =
|
|
32
|
+
| "top-left" | "top-center" | "top-right"
|
|
33
|
+
| "bottom-left" | "bottom-center" | "bottom-right"
|
|
34
|
+
| "left-top" | "left-center" | "left-bottom"
|
|
35
|
+
| "right-top" | "right-center" | "right-bottom"
|
|
36
|
+
| "above" | "below";
|
|
37
|
+
|
|
28
38
|
export interface OverlappingCardsScrollTabProps {
|
|
29
39
|
name: string;
|
|
30
40
|
index: number;
|
|
31
|
-
position:
|
|
41
|
+
position: TabsPositionSide;
|
|
42
|
+
align: TabsPositionAlign;
|
|
32
43
|
isPrincipal: boolean;
|
|
33
44
|
influence: number;
|
|
34
|
-
animate: {
|
|
35
|
-
opacity: number;
|
|
36
|
-
};
|
|
45
|
+
animate: { opacity: number };
|
|
37
46
|
className: string;
|
|
38
47
|
style: CSSProperties;
|
|
39
48
|
ariaLabel: string;
|
|
@@ -43,7 +52,8 @@ export interface OverlappingCardsScrollTabProps {
|
|
|
43
52
|
|
|
44
53
|
export interface OverlappingCardsScrollTabsContainerProps {
|
|
45
54
|
children: ReactNode;
|
|
46
|
-
position:
|
|
55
|
+
position: TabsPositionSide;
|
|
56
|
+
align: TabsPositionAlign;
|
|
47
57
|
className: string;
|
|
48
58
|
style: CSSProperties;
|
|
49
59
|
ariaLabel: string;
|
|
@@ -72,7 +82,7 @@ type SharedProps = {
|
|
|
72
82
|
focusTransitionDuration?: number;
|
|
73
83
|
ariaLabel?: string;
|
|
74
84
|
showTabs?: boolean;
|
|
75
|
-
tabsPosition?:
|
|
85
|
+
tabsPosition?: TabsPosition;
|
|
76
86
|
tabsOffset?: number | string;
|
|
77
87
|
tabsBehavior?: "smooth" | "auto";
|
|
78
88
|
tabsClassName?: string;
|
|
@@ -101,10 +111,33 @@ const PAGE_DOT_POSITIONS = new Set(["above", "below", "overlay"]);
|
|
|
101
111
|
const normalizePageDotsPosition = (value) =>
|
|
102
112
|
PAGE_DOT_POSITIONS.has(value) ? value : "below";
|
|
103
113
|
|
|
104
|
-
|
|
114
|
+
interface ParsedTabsPosition {
|
|
115
|
+
side: TabsPositionSide;
|
|
116
|
+
align: TabsPositionAlign;
|
|
117
|
+
orientation: "horizontal" | "vertical";
|
|
118
|
+
}
|
|
105
119
|
|
|
106
|
-
const
|
|
107
|
-
|
|
120
|
+
const TABS_POSITION_MAP: Record<string, ParsedTabsPosition> = {
|
|
121
|
+
"top-left": { side: "top", align: "start", orientation: "horizontal" },
|
|
122
|
+
"top-center": { side: "top", align: "center", orientation: "horizontal" },
|
|
123
|
+
"top-right": { side: "top", align: "end", orientation: "horizontal" },
|
|
124
|
+
"bottom-left": { side: "bottom", align: "start", orientation: "horizontal" },
|
|
125
|
+
"bottom-center": { side: "bottom", align: "center", orientation: "horizontal" },
|
|
126
|
+
"bottom-right": { side: "bottom", align: "end", orientation: "horizontal" },
|
|
127
|
+
"left-top": { side: "left", align: "start", orientation: "vertical" },
|
|
128
|
+
"left-center": { side: "left", align: "center", orientation: "vertical" },
|
|
129
|
+
"left-bottom": { side: "left", align: "end", orientation: "vertical" },
|
|
130
|
+
"right-top": { side: "right", align: "start", orientation: "vertical" },
|
|
131
|
+
"right-center": { side: "right", align: "center", orientation: "vertical" },
|
|
132
|
+
"right-bottom": { side: "right", align: "end", orientation: "vertical" },
|
|
133
|
+
"above": { side: "top", align: "center", orientation: "horizontal" },
|
|
134
|
+
"below": { side: "bottom", align: "center", orientation: "horizontal" },
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const DEFAULT_TABS_POSITION: ParsedTabsPosition = { side: "top", align: "center", orientation: "horizontal" };
|
|
138
|
+
|
|
139
|
+
const parseTabsPosition = (value: string | undefined): ParsedTabsPosition =>
|
|
140
|
+
(value && TABS_POSITION_MAP[value]) || DEFAULT_TABS_POSITION;
|
|
108
141
|
|
|
109
142
|
// Persist across HMR so remount gets a valid fallback instead of 1
|
|
110
143
|
let lastKnownViewportWidth = 1;
|
|
@@ -328,6 +361,7 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
328
361
|
const cardCount = cards.length;
|
|
329
362
|
|
|
330
363
|
const containerRef = useRef(null);
|
|
364
|
+
const stageRef = useRef(null);
|
|
331
365
|
const scrollRef = useRef(null);
|
|
332
366
|
const touchStateRef = useRef(null);
|
|
333
367
|
const snapTimeoutRef = useRef(null);
|
|
@@ -358,9 +392,9 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
358
392
|
}, [clearFocusTransitionTimeout]);
|
|
359
393
|
|
|
360
394
|
useEffect(() => {
|
|
361
|
-
const
|
|
395
|
+
const stageElement = stageRef.current;
|
|
362
396
|
const scrollElement = scrollRef.current;
|
|
363
|
-
if (!
|
|
397
|
+
if (!stageElement || !scrollElement) {
|
|
364
398
|
return undefined;
|
|
365
399
|
}
|
|
366
400
|
|
|
@@ -384,8 +418,8 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
384
418
|
syncScroll();
|
|
385
419
|
});
|
|
386
420
|
|
|
387
|
-
resizeObserver.observe(
|
|
388
|
-
applyWidth(
|
|
421
|
+
resizeObserver.observe(stageElement);
|
|
422
|
+
applyWidth(stageElement.getBoundingClientRect().width ?? 0);
|
|
389
423
|
syncScroll();
|
|
390
424
|
|
|
391
425
|
scrollElement.addEventListener("scroll", syncScroll, { passive: true });
|
|
@@ -770,8 +804,6 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
770
804
|
touchStateRef.current = null;
|
|
771
805
|
};
|
|
772
806
|
|
|
773
|
-
const stageRef = useRef(null);
|
|
774
|
-
|
|
775
807
|
useEffect(() => {
|
|
776
808
|
const stageElement = stageRef.current;
|
|
777
809
|
if (!stageElement) {
|
|
@@ -784,15 +816,21 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
784
816
|
};
|
|
785
817
|
}, [handleWheel]);
|
|
786
818
|
|
|
787
|
-
const containerClassName = className
|
|
788
|
-
? `overlapping-cards-scroll ${className}`
|
|
789
|
-
: "overlapping-cards-scroll";
|
|
790
819
|
const resolvedPageDotsPosition = normalizePageDotsPosition(pageDotsPosition);
|
|
791
820
|
const showNavigationDots = showPageDots && cardCount > 1;
|
|
792
821
|
|
|
793
|
-
const
|
|
822
|
+
const parsedTabsPosition = parseTabsPosition(tabsPosition);
|
|
823
|
+
const isVerticalTabs = parsedTabsPosition.orientation === "vertical";
|
|
794
824
|
const showNavigationTabs = showTabs && cardCount > 1 && cardNames !== null;
|
|
795
825
|
|
|
826
|
+
const containerClassName = [
|
|
827
|
+
"overlapping-cards-scroll",
|
|
828
|
+
isVerticalTabs ? "overlapping-cards-scroll--vertical-tabs" : "",
|
|
829
|
+
className,
|
|
830
|
+
]
|
|
831
|
+
.filter(Boolean)
|
|
832
|
+
.join(" ");
|
|
833
|
+
|
|
796
834
|
useEffect(() => {
|
|
797
835
|
if (showTabs && cardNames === null) {
|
|
798
836
|
console.warn(
|
|
@@ -801,24 +839,44 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
801
839
|
}
|
|
802
840
|
}, [showTabs, cardNames]);
|
|
803
841
|
|
|
804
|
-
const renderTabs = (
|
|
842
|
+
const renderTabs = () => {
|
|
805
843
|
if (!showNavigationTabs || cardNames === null) {
|
|
806
844
|
return null;
|
|
807
845
|
}
|
|
808
846
|
|
|
809
|
-
const
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
847
|
+
const { side, align, orientation } = parsedTabsPosition;
|
|
848
|
+
const isVertical = orientation === "vertical";
|
|
849
|
+
|
|
850
|
+
const alignClass =
|
|
851
|
+
align === "start"
|
|
852
|
+
? "ocs-tabs--align-start"
|
|
853
|
+
: align === "end"
|
|
854
|
+
? "ocs-tabs--align-end"
|
|
855
|
+
: "ocs-tabs--align-center";
|
|
856
|
+
|
|
857
|
+
const orientationClass = isVertical ? "ocs-tabs--vertical" : "";
|
|
858
|
+
|
|
859
|
+
const classNames = [
|
|
860
|
+
"ocs-tabs",
|
|
861
|
+
`ocs-tabs--${side}`,
|
|
862
|
+
alignClass,
|
|
863
|
+
orientationClass,
|
|
864
|
+
tabsClassName,
|
|
865
|
+
]
|
|
866
|
+
.filter(Boolean)
|
|
867
|
+
.join(" ");
|
|
868
|
+
|
|
869
|
+
const containerStyle: CSSProperties = {};
|
|
870
|
+
if (side === "top") containerStyle.marginBottom = toCssDimension(tabsOffset);
|
|
871
|
+
else if (side === "bottom") containerStyle.marginTop = toCssDimension(tabsOffset);
|
|
872
|
+
else if (side === "left") containerStyle.marginRight = toCssDimension(tabsOffset);
|
|
873
|
+
else if (side === "right") containerStyle.marginLeft = toCssDimension(tabsOffset);
|
|
817
874
|
|
|
818
875
|
return (
|
|
819
876
|
<TabsContainerComponent
|
|
820
|
-
position={
|
|
821
|
-
|
|
877
|
+
position={side}
|
|
878
|
+
align={align}
|
|
879
|
+
className={classNames}
|
|
822
880
|
style={containerStyle}
|
|
823
881
|
ariaLabel="Card tabs"
|
|
824
882
|
cardNames={cardNames}
|
|
@@ -828,18 +886,17 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
828
886
|
{cardNames.map((name, index) => {
|
|
829
887
|
const influence = clamp(1 - Math.abs(progress - index), 0, 1);
|
|
830
888
|
const isPrincipal = influence > 0.98;
|
|
831
|
-
const animate = {
|
|
832
|
-
opacity: 0.45 + influence * 0.55,
|
|
833
|
-
};
|
|
889
|
+
const animate = { opacity: 0.45 + influence * 0.55 };
|
|
834
890
|
const className = isPrincipal ? "ocs-tab ocs-tab--active" : "ocs-tab";
|
|
835
891
|
const style = { opacity: animate.opacity };
|
|
836
892
|
|
|
837
893
|
return (
|
|
838
894
|
<TabsComponent
|
|
839
|
-
key={`ocs-tab-${
|
|
895
|
+
key={`ocs-tab-${side}-${index}`}
|
|
840
896
|
name={name}
|
|
841
897
|
index={index}
|
|
842
|
-
position={
|
|
898
|
+
position={side}
|
|
899
|
+
align={align}
|
|
843
900
|
isPrincipal={isPrincipal}
|
|
844
901
|
influence={influence}
|
|
845
902
|
animate={animate}
|
|
@@ -860,160 +917,120 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
860
917
|
);
|
|
861
918
|
};
|
|
862
919
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
920
|
+
const tabsBeforeStage =
|
|
921
|
+
parsedTabsPosition.side === "top" || parsedTabsPosition.side === "left";
|
|
922
|
+
|
|
923
|
+
const stageAndDots = (
|
|
924
|
+
<>
|
|
925
|
+
{showNavigationDots && resolvedPageDotsPosition === "above" ? (
|
|
926
|
+
<nav
|
|
927
|
+
className={
|
|
928
|
+
pageDotsClassName
|
|
929
|
+
? `ocs-page-dots ocs-page-dots--above ${pageDotsClassName}`
|
|
930
|
+
: "ocs-page-dots ocs-page-dots--above"
|
|
931
|
+
}
|
|
932
|
+
style={{ marginBottom: toCssDimension(pageDotsOffset) }}
|
|
933
|
+
aria-label="Card pages"
|
|
934
|
+
>
|
|
935
|
+
{cards.map((_, index) => {
|
|
936
|
+
const influence = clamp(1 - Math.abs(progress - index), 0, 1);
|
|
937
|
+
const opacity = 0.25 + influence * 0.75;
|
|
938
|
+
const scale = 0.9 + influence * 0.22;
|
|
939
|
+
|
|
940
|
+
return (
|
|
941
|
+
<button
|
|
942
|
+
key={`ocs-page-dot-above-${index}`}
|
|
943
|
+
type="button"
|
|
944
|
+
className="ocs-page-dot"
|
|
945
|
+
aria-label={`Go to card ${index + 1}`}
|
|
946
|
+
aria-current={influence > 0.98 ? "page" : undefined}
|
|
947
|
+
onClick={() =>
|
|
948
|
+
focusCard(index, {
|
|
949
|
+
behavior: pageDotsBehavior,
|
|
950
|
+
transitionMode: "swoop",
|
|
951
|
+
})
|
|
952
|
+
}
|
|
953
|
+
style={{ opacity, transform: `scale(${scale})` }}
|
|
954
|
+
/>
|
|
955
|
+
);
|
|
956
|
+
})}
|
|
957
|
+
</nav>
|
|
958
|
+
) : null}
|
|
959
|
+
<div className="ocs-stage-frame">
|
|
960
|
+
<div
|
|
961
|
+
className="ocs-stage"
|
|
962
|
+
ref={stageRef}
|
|
963
|
+
style={{
|
|
964
|
+
minHeight: toCssDimension(cardHeight),
|
|
965
|
+
}}
|
|
966
|
+
onTouchStart={handleTouchStart}
|
|
967
|
+
onTouchMove={handleTouchMove}
|
|
968
|
+
onTouchEnd={handleTouchEnd}
|
|
969
|
+
onTouchCancel={handleTouchEnd}
|
|
970
|
+
>
|
|
971
|
+
<div className="ocs-track">
|
|
972
|
+
{cards.map((card, index) => {
|
|
973
|
+
const cardX = resolveCardX(
|
|
974
|
+
index,
|
|
975
|
+
activeIndex,
|
|
976
|
+
transitionProgress,
|
|
977
|
+
layout,
|
|
978
|
+
);
|
|
887
979
|
|
|
888
980
|
return (
|
|
889
|
-
<
|
|
890
|
-
key={`ocs-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
behavior: pageDotsBehavior,
|
|
898
|
-
transitionMode: "swoop",
|
|
899
|
-
})
|
|
981
|
+
<div
|
|
982
|
+
key={card.key ?? `ocs-card-${index}`}
|
|
983
|
+
className={
|
|
984
|
+
cardContainerClassName
|
|
985
|
+
? `${focusTransition ? "ocs-card ocs-card--focus-transition" : "ocs-card"} ${cardContainerClassName}`
|
|
986
|
+
: focusTransition
|
|
987
|
+
? "ocs-card ocs-card--focus-transition"
|
|
988
|
+
: "ocs-card"
|
|
900
989
|
}
|
|
901
|
-
style={{
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
style={{
|
|
912
|
-
minHeight: toCssDimension(cardHeight),
|
|
913
|
-
}}
|
|
914
|
-
onTouchStart={handleTouchStart}
|
|
915
|
-
onTouchMove={handleTouchMove}
|
|
916
|
-
onTouchEnd={handleTouchEnd}
|
|
917
|
-
onTouchCancel={handleTouchEnd}
|
|
918
|
-
>
|
|
919
|
-
<div className="ocs-track">
|
|
920
|
-
{cards.map((card, index) => {
|
|
921
|
-
const cardX = resolveCardX(
|
|
922
|
-
index,
|
|
923
|
-
activeIndex,
|
|
924
|
-
transitionProgress,
|
|
925
|
-
layout,
|
|
926
|
-
);
|
|
927
|
-
|
|
928
|
-
return (
|
|
990
|
+
style={{
|
|
991
|
+
width: `${layout.cardWidth}px`,
|
|
992
|
+
transform: `translate3d(${cardX}px, 0, 0)`,
|
|
993
|
+
transitionDuration: focusTransition
|
|
994
|
+
? `${focusTransition.duration}ms`
|
|
995
|
+
: undefined,
|
|
996
|
+
...cardContainerStyle,
|
|
997
|
+
pointerEvents: "none",
|
|
998
|
+
}}
|
|
999
|
+
>
|
|
929
1000
|
<div
|
|
930
|
-
key={card.key ?? `ocs-card-${index}`}
|
|
931
|
-
className={
|
|
932
|
-
cardContainerClassName
|
|
933
|
-
? `${focusTransition ? "ocs-card ocs-card--focus-transition" : "ocs-card"} ${cardContainerClassName}`
|
|
934
|
-
: focusTransition
|
|
935
|
-
? "ocs-card ocs-card--focus-transition"
|
|
936
|
-
: "ocs-card"
|
|
937
|
-
}
|
|
938
1001
|
style={{
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
? `${focusTransition.duration}ms`
|
|
943
|
-
: undefined,
|
|
944
|
-
...cardContainerStyle,
|
|
945
|
-
pointerEvents: "none",
|
|
1002
|
+
pointerEvents: "auto",
|
|
1003
|
+
display: "flex",
|
|
1004
|
+
flexDirection: "column",
|
|
946
1005
|
}}
|
|
947
1006
|
>
|
|
948
|
-
<
|
|
949
|
-
|
|
950
|
-
pointerEvents: "auto",
|
|
951
|
-
display: "flex",
|
|
952
|
-
flexDirection: "column",
|
|
953
|
-
}}
|
|
1007
|
+
<OverlappingCardsScrollCardIndexContext.Provider
|
|
1008
|
+
value={index}
|
|
954
1009
|
>
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
>
|
|
958
|
-
{card}
|
|
959
|
-
</OverlappingCardsScrollCardIndexContext.Provider>
|
|
960
|
-
</div>
|
|
1010
|
+
{card}
|
|
1011
|
+
</OverlappingCardsScrollCardIndexContext.Provider>
|
|
961
1012
|
</div>
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
|
|
1013
|
+
</div>
|
|
1014
|
+
);
|
|
1015
|
+
})}
|
|
1016
|
+
</div>
|
|
1017
|
+
<div className="ocs-scroll-region" ref={scrollRef}>
|
|
1018
|
+
<div
|
|
1019
|
+
className="ocs-scroll-spacer"
|
|
1020
|
+
style={{
|
|
1021
|
+
width: `${layout.trackWidth}px`,
|
|
1022
|
+
}}
|
|
1023
|
+
/>
|
|
973
1024
|
</div>
|
|
974
|
-
{showNavigationDots && resolvedPageDotsPosition === "overlay" ? (
|
|
975
|
-
<nav
|
|
976
|
-
className={
|
|
977
|
-
pageDotsClassName
|
|
978
|
-
? `ocs-page-dots ocs-page-dots--overlay ${pageDotsClassName}`
|
|
979
|
-
: "ocs-page-dots ocs-page-dots--overlay"
|
|
980
|
-
}
|
|
981
|
-
style={{ bottom: toCssDimension(pageDotsOffset) }}
|
|
982
|
-
aria-label="Card pages"
|
|
983
|
-
>
|
|
984
|
-
{cards.map((_, index) => {
|
|
985
|
-
const influence = clamp(1 - Math.abs(progress - index), 0, 1);
|
|
986
|
-
const opacity = 0.25 + influence * 0.75;
|
|
987
|
-
const scale = 0.9 + influence * 0.22;
|
|
988
|
-
|
|
989
|
-
return (
|
|
990
|
-
<button
|
|
991
|
-
key={`ocs-page-dot-overlay-${index}`}
|
|
992
|
-
type="button"
|
|
993
|
-
className="ocs-page-dot"
|
|
994
|
-
aria-label={`Go to card ${index + 1}`}
|
|
995
|
-
aria-current={influence > 0.98 ? "page" : undefined}
|
|
996
|
-
onClick={() =>
|
|
997
|
-
focusCard(index, {
|
|
998
|
-
behavior: pageDotsBehavior,
|
|
999
|
-
transitionMode: "swoop",
|
|
1000
|
-
})
|
|
1001
|
-
}
|
|
1002
|
-
style={{ opacity, transform: `scale(${scale})` }}
|
|
1003
|
-
/>
|
|
1004
|
-
);
|
|
1005
|
-
})}
|
|
1006
|
-
</nav>
|
|
1007
|
-
) : null}
|
|
1008
1025
|
</div>
|
|
1009
|
-
{showNavigationDots && resolvedPageDotsPosition === "
|
|
1026
|
+
{showNavigationDots && resolvedPageDotsPosition === "overlay" ? (
|
|
1010
1027
|
<nav
|
|
1011
1028
|
className={
|
|
1012
1029
|
pageDotsClassName
|
|
1013
|
-
? `ocs-page-dots ocs-page-dots--
|
|
1014
|
-
: "ocs-page-dots ocs-page-dots--
|
|
1030
|
+
? `ocs-page-dots ocs-page-dots--overlay ${pageDotsClassName}`
|
|
1031
|
+
: "ocs-page-dots ocs-page-dots--overlay"
|
|
1015
1032
|
}
|
|
1016
|
-
style={{
|
|
1033
|
+
style={{ bottom: toCssDimension(pageDotsOffset) }}
|
|
1017
1034
|
aria-label="Card pages"
|
|
1018
1035
|
>
|
|
1019
1036
|
{cards.map((_, index) => {
|
|
@@ -1023,7 +1040,7 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
1023
1040
|
|
|
1024
1041
|
return (
|
|
1025
1042
|
<button
|
|
1026
|
-
key={`ocs-page-dot-
|
|
1043
|
+
key={`ocs-page-dot-overlay-${index}`}
|
|
1027
1044
|
type="button"
|
|
1028
1045
|
className="ocs-page-dot"
|
|
1029
1046
|
aria-label={`Go to card ${index + 1}`}
|
|
@@ -1040,7 +1057,62 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
1040
1057
|
})}
|
|
1041
1058
|
</nav>
|
|
1042
1059
|
) : null}
|
|
1043
|
-
|
|
1060
|
+
</div>
|
|
1061
|
+
{showNavigationDots && resolvedPageDotsPosition === "below" ? (
|
|
1062
|
+
<nav
|
|
1063
|
+
className={
|
|
1064
|
+
pageDotsClassName
|
|
1065
|
+
? `ocs-page-dots ocs-page-dots--below ${pageDotsClassName}`
|
|
1066
|
+
: "ocs-page-dots ocs-page-dots--below"
|
|
1067
|
+
}
|
|
1068
|
+
style={{ marginTop: toCssDimension(pageDotsOffset) }}
|
|
1069
|
+
aria-label="Card pages"
|
|
1070
|
+
>
|
|
1071
|
+
{cards.map((_, index) => {
|
|
1072
|
+
const influence = clamp(1 - Math.abs(progress - index), 0, 1);
|
|
1073
|
+
const opacity = 0.25 + influence * 0.75;
|
|
1074
|
+
const scale = 0.9 + influence * 0.22;
|
|
1075
|
+
|
|
1076
|
+
return (
|
|
1077
|
+
<button
|
|
1078
|
+
key={`ocs-page-dot-below-${index}`}
|
|
1079
|
+
type="button"
|
|
1080
|
+
className="ocs-page-dot"
|
|
1081
|
+
aria-label={`Go to card ${index + 1}`}
|
|
1082
|
+
aria-current={influence > 0.98 ? "page" : undefined}
|
|
1083
|
+
onClick={() =>
|
|
1084
|
+
focusCard(index, {
|
|
1085
|
+
behavior: pageDotsBehavior,
|
|
1086
|
+
transitionMode: "swoop",
|
|
1087
|
+
})
|
|
1088
|
+
}
|
|
1089
|
+
style={{ opacity, transform: `scale(${scale})` }}
|
|
1090
|
+
/>
|
|
1091
|
+
);
|
|
1092
|
+
})}
|
|
1093
|
+
</nav>
|
|
1094
|
+
) : null}
|
|
1095
|
+
</>
|
|
1096
|
+
);
|
|
1097
|
+
|
|
1098
|
+
const stageContent = isVerticalTabs ? (
|
|
1099
|
+
<div className="ocs-main-column">{stageAndDots}</div>
|
|
1100
|
+
) : (
|
|
1101
|
+
stageAndDots
|
|
1102
|
+
);
|
|
1103
|
+
|
|
1104
|
+
return (
|
|
1105
|
+
<OverlappingCardsScrollControllerContext.Provider
|
|
1106
|
+
value={controllerContextValue}
|
|
1107
|
+
>
|
|
1108
|
+
<section
|
|
1109
|
+
className={containerClassName}
|
|
1110
|
+
aria-label={ariaLabel}
|
|
1111
|
+
ref={containerRef}
|
|
1112
|
+
>
|
|
1113
|
+
{tabsBeforeStage ? renderTabs() : null}
|
|
1114
|
+
{stageContent}
|
|
1115
|
+
{!tabsBeforeStage ? renderTabs() : null}
|
|
1044
1116
|
</section>
|
|
1045
1117
|
</OverlappingCardsScrollControllerContext.Provider>
|
|
1046
1118
|
);
|