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 +207 -166
- package/dist/index.js +208 -167
- package/dist/react-native-web.cjs +207 -166
- package/dist/react-native-web.js +208 -167
- package/dist/react-native.cjs +157 -113
- package/dist/react-native.js +158 -114
- 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 -169
- package/src/lib/index.ts +3 -0
- package/src/rn/OverlappingCardsScrollRN.native.tsx +204 -136
- package/src/rn/OverlappingCardsScrollRN.types.ts +5 -1
|
@@ -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;
|
|
@@ -784,15 +817,21 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
784
817
|
};
|
|
785
818
|
}, [handleWheel]);
|
|
786
819
|
|
|
787
|
-
const containerClassName = className
|
|
788
|
-
? `overlapping-cards-scroll ${className}`
|
|
789
|
-
: "overlapping-cards-scroll";
|
|
790
820
|
const resolvedPageDotsPosition = normalizePageDotsPosition(pageDotsPosition);
|
|
791
821
|
const showNavigationDots = showPageDots && cardCount > 1;
|
|
792
822
|
|
|
793
|
-
const
|
|
823
|
+
const parsedTabsPosition = parseTabsPosition(tabsPosition);
|
|
824
|
+
const isVerticalTabs = parsedTabsPosition.orientation === "vertical";
|
|
794
825
|
const showNavigationTabs = showTabs && cardCount > 1 && cardNames !== null;
|
|
795
826
|
|
|
827
|
+
const containerClassName = [
|
|
828
|
+
"overlapping-cards-scroll",
|
|
829
|
+
isVerticalTabs ? "overlapping-cards-scroll--vertical-tabs" : "",
|
|
830
|
+
className,
|
|
831
|
+
]
|
|
832
|
+
.filter(Boolean)
|
|
833
|
+
.join(" ");
|
|
834
|
+
|
|
796
835
|
useEffect(() => {
|
|
797
836
|
if (showTabs && cardNames === null) {
|
|
798
837
|
console.warn(
|
|
@@ -801,24 +840,44 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
801
840
|
}
|
|
802
841
|
}, [showTabs, cardNames]);
|
|
803
842
|
|
|
804
|
-
const renderTabs = (
|
|
843
|
+
const renderTabs = () => {
|
|
805
844
|
if (!showNavigationTabs || cardNames === null) {
|
|
806
845
|
return null;
|
|
807
846
|
}
|
|
808
847
|
|
|
809
|
-
const
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
848
|
+
const { side, align, orientation } = parsedTabsPosition;
|
|
849
|
+
const isVertical = orientation === "vertical";
|
|
850
|
+
|
|
851
|
+
const alignClass =
|
|
852
|
+
align === "start"
|
|
853
|
+
? "ocs-tabs--align-start"
|
|
854
|
+
: align === "end"
|
|
855
|
+
? "ocs-tabs--align-end"
|
|
856
|
+
: "ocs-tabs--align-center";
|
|
857
|
+
|
|
858
|
+
const orientationClass = isVertical ? "ocs-tabs--vertical" : "";
|
|
859
|
+
|
|
860
|
+
const classNames = [
|
|
861
|
+
"ocs-tabs",
|
|
862
|
+
`ocs-tabs--${side}`,
|
|
863
|
+
alignClass,
|
|
864
|
+
orientationClass,
|
|
865
|
+
tabsClassName,
|
|
866
|
+
]
|
|
867
|
+
.filter(Boolean)
|
|
868
|
+
.join(" ");
|
|
869
|
+
|
|
870
|
+
const containerStyle: CSSProperties = {};
|
|
871
|
+
if (side === "top") containerStyle.marginBottom = toCssDimension(tabsOffset);
|
|
872
|
+
else if (side === "bottom") containerStyle.marginTop = toCssDimension(tabsOffset);
|
|
873
|
+
else if (side === "left") containerStyle.marginRight = toCssDimension(tabsOffset);
|
|
874
|
+
else if (side === "right") containerStyle.marginLeft = toCssDimension(tabsOffset);
|
|
817
875
|
|
|
818
876
|
return (
|
|
819
877
|
<TabsContainerComponent
|
|
820
|
-
position={
|
|
821
|
-
|
|
878
|
+
position={side}
|
|
879
|
+
align={align}
|
|
880
|
+
className={classNames}
|
|
822
881
|
style={containerStyle}
|
|
823
882
|
ariaLabel="Card tabs"
|
|
824
883
|
cardNames={cardNames}
|
|
@@ -828,18 +887,17 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
828
887
|
{cardNames.map((name, index) => {
|
|
829
888
|
const influence = clamp(1 - Math.abs(progress - index), 0, 1);
|
|
830
889
|
const isPrincipal = influence > 0.98;
|
|
831
|
-
const animate = {
|
|
832
|
-
opacity: 0.45 + influence * 0.55,
|
|
833
|
-
};
|
|
890
|
+
const animate = { opacity: 0.45 + influence * 0.55 };
|
|
834
891
|
const className = isPrincipal ? "ocs-tab ocs-tab--active" : "ocs-tab";
|
|
835
892
|
const style = { opacity: animate.opacity };
|
|
836
893
|
|
|
837
894
|
return (
|
|
838
895
|
<TabsComponent
|
|
839
|
-
key={`ocs-tab-${
|
|
896
|
+
key={`ocs-tab-${side}-${index}`}
|
|
840
897
|
name={name}
|
|
841
898
|
index={index}
|
|
842
|
-
position={
|
|
899
|
+
position={side}
|
|
900
|
+
align={align}
|
|
843
901
|
isPrincipal={isPrincipal}
|
|
844
902
|
influence={influence}
|
|
845
903
|
animate={animate}
|
|
@@ -860,159 +918,120 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
860
918
|
);
|
|
861
919
|
};
|
|
862
920
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
921
|
+
const tabsBeforeStage =
|
|
922
|
+
parsedTabsPosition.side === "top" || parsedTabsPosition.side === "left";
|
|
923
|
+
|
|
924
|
+
const stageAndDots = (
|
|
925
|
+
<>
|
|
926
|
+
{showNavigationDots && resolvedPageDotsPosition === "above" ? (
|
|
927
|
+
<nav
|
|
928
|
+
className={
|
|
929
|
+
pageDotsClassName
|
|
930
|
+
? `ocs-page-dots ocs-page-dots--above ${pageDotsClassName}`
|
|
931
|
+
: "ocs-page-dots ocs-page-dots--above"
|
|
932
|
+
}
|
|
933
|
+
style={{ marginBottom: toCssDimension(pageDotsOffset) }}
|
|
934
|
+
aria-label="Card pages"
|
|
935
|
+
>
|
|
936
|
+
{cards.map((_, index) => {
|
|
937
|
+
const influence = clamp(1 - Math.abs(progress - index), 0, 1);
|
|
938
|
+
const opacity = 0.25 + influence * 0.75;
|
|
939
|
+
const scale = 0.9 + influence * 0.22;
|
|
940
|
+
|
|
941
|
+
return (
|
|
942
|
+
<button
|
|
943
|
+
key={`ocs-page-dot-above-${index}`}
|
|
944
|
+
type="button"
|
|
945
|
+
className="ocs-page-dot"
|
|
946
|
+
aria-label={`Go to card ${index + 1}`}
|
|
947
|
+
aria-current={influence > 0.98 ? "page" : undefined}
|
|
948
|
+
onClick={() =>
|
|
949
|
+
focusCard(index, {
|
|
950
|
+
behavior: pageDotsBehavior,
|
|
951
|
+
transitionMode: "swoop",
|
|
952
|
+
})
|
|
953
|
+
}
|
|
954
|
+
style={{ opacity, transform: `scale(${scale})` }}
|
|
955
|
+
/>
|
|
956
|
+
);
|
|
957
|
+
})}
|
|
958
|
+
</nav>
|
|
959
|
+
) : null}
|
|
960
|
+
<div className="ocs-stage-frame">
|
|
961
|
+
<div
|
|
962
|
+
className="ocs-stage"
|
|
963
|
+
ref={stageRef}
|
|
964
|
+
style={{
|
|
965
|
+
minHeight: toCssDimension(cardHeight),
|
|
966
|
+
}}
|
|
967
|
+
onTouchStart={handleTouchStart}
|
|
968
|
+
onTouchMove={handleTouchMove}
|
|
969
|
+
onTouchEnd={handleTouchEnd}
|
|
970
|
+
onTouchCancel={handleTouchEnd}
|
|
971
|
+
>
|
|
972
|
+
<div className="ocs-track">
|
|
973
|
+
{cards.map((card, index) => {
|
|
974
|
+
const cardX = resolveCardX(
|
|
975
|
+
index,
|
|
976
|
+
activeIndex,
|
|
977
|
+
transitionProgress,
|
|
978
|
+
layout,
|
|
979
|
+
);
|
|
887
980
|
|
|
888
981
|
return (
|
|
889
|
-
<
|
|
890
|
-
key={`ocs-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
behavior: pageDotsBehavior,
|
|
898
|
-
transitionMode: "swoop",
|
|
899
|
-
})
|
|
982
|
+
<div
|
|
983
|
+
key={card.key ?? `ocs-card-${index}`}
|
|
984
|
+
className={
|
|
985
|
+
cardContainerClassName
|
|
986
|
+
? `${focusTransition ? "ocs-card ocs-card--focus-transition" : "ocs-card"} ${cardContainerClassName}`
|
|
987
|
+
: focusTransition
|
|
988
|
+
? "ocs-card ocs-card--focus-transition"
|
|
989
|
+
: "ocs-card"
|
|
900
990
|
}
|
|
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 (
|
|
991
|
+
style={{
|
|
992
|
+
width: `${layout.cardWidth}px`,
|
|
993
|
+
transform: `translate3d(${cardX}px, 0, 0)`,
|
|
994
|
+
transitionDuration: focusTransition
|
|
995
|
+
? `${focusTransition.duration}ms`
|
|
996
|
+
: undefined,
|
|
997
|
+
...cardContainerStyle,
|
|
998
|
+
pointerEvents: "none",
|
|
999
|
+
}}
|
|
1000
|
+
>
|
|
929
1001
|
<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
1002
|
style={{
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
? `${focusTransition.duration}ms`
|
|
943
|
-
: undefined,
|
|
944
|
-
...cardContainerStyle,
|
|
945
|
-
pointerEvents: "none",
|
|
1003
|
+
pointerEvents: "auto",
|
|
1004
|
+
display: "flex",
|
|
1005
|
+
flexDirection: "column",
|
|
946
1006
|
}}
|
|
947
1007
|
>
|
|
948
|
-
<
|
|
949
|
-
|
|
950
|
-
pointerEvents: "auto",
|
|
951
|
-
display: "flex",
|
|
952
|
-
}}
|
|
1008
|
+
<OverlappingCardsScrollCardIndexContext.Provider
|
|
1009
|
+
value={index}
|
|
953
1010
|
>
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
>
|
|
957
|
-
{card}
|
|
958
|
-
</OverlappingCardsScrollCardIndexContext.Provider>
|
|
959
|
-
</div>
|
|
1011
|
+
{card}
|
|
1012
|
+
</OverlappingCardsScrollCardIndexContext.Provider>
|
|
960
1013
|
</div>
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
|
|
1014
|
+
</div>
|
|
1015
|
+
);
|
|
1016
|
+
})}
|
|
1017
|
+
</div>
|
|
1018
|
+
<div className="ocs-scroll-region" ref={scrollRef}>
|
|
1019
|
+
<div
|
|
1020
|
+
className="ocs-scroll-spacer"
|
|
1021
|
+
style={{
|
|
1022
|
+
width: `${layout.trackWidth}px`,
|
|
1023
|
+
}}
|
|
1024
|
+
/>
|
|
972
1025
|
</div>
|
|
973
|
-
{showNavigationDots && resolvedPageDotsPosition === "overlay" ? (
|
|
974
|
-
<nav
|
|
975
|
-
className={
|
|
976
|
-
pageDotsClassName
|
|
977
|
-
? `ocs-page-dots ocs-page-dots--overlay ${pageDotsClassName}`
|
|
978
|
-
: "ocs-page-dots ocs-page-dots--overlay"
|
|
979
|
-
}
|
|
980
|
-
style={{ bottom: toCssDimension(pageDotsOffset) }}
|
|
981
|
-
aria-label="Card pages"
|
|
982
|
-
>
|
|
983
|
-
{cards.map((_, index) => {
|
|
984
|
-
const influence = clamp(1 - Math.abs(progress - index), 0, 1);
|
|
985
|
-
const opacity = 0.25 + influence * 0.75;
|
|
986
|
-
const scale = 0.9 + influence * 0.22;
|
|
987
|
-
|
|
988
|
-
return (
|
|
989
|
-
<button
|
|
990
|
-
key={`ocs-page-dot-overlay-${index}`}
|
|
991
|
-
type="button"
|
|
992
|
-
className="ocs-page-dot"
|
|
993
|
-
aria-label={`Go to card ${index + 1}`}
|
|
994
|
-
aria-current={influence > 0.98 ? "page" : undefined}
|
|
995
|
-
onClick={() =>
|
|
996
|
-
focusCard(index, {
|
|
997
|
-
behavior: pageDotsBehavior,
|
|
998
|
-
transitionMode: "swoop",
|
|
999
|
-
})
|
|
1000
|
-
}
|
|
1001
|
-
style={{ opacity, transform: `scale(${scale})` }}
|
|
1002
|
-
/>
|
|
1003
|
-
);
|
|
1004
|
-
})}
|
|
1005
|
-
</nav>
|
|
1006
|
-
) : null}
|
|
1007
1026
|
</div>
|
|
1008
|
-
{showNavigationDots && resolvedPageDotsPosition === "
|
|
1027
|
+
{showNavigationDots && resolvedPageDotsPosition === "overlay" ? (
|
|
1009
1028
|
<nav
|
|
1010
1029
|
className={
|
|
1011
1030
|
pageDotsClassName
|
|
1012
|
-
? `ocs-page-dots ocs-page-dots--
|
|
1013
|
-
: "ocs-page-dots ocs-page-dots--
|
|
1031
|
+
? `ocs-page-dots ocs-page-dots--overlay ${pageDotsClassName}`
|
|
1032
|
+
: "ocs-page-dots ocs-page-dots--overlay"
|
|
1014
1033
|
}
|
|
1015
|
-
style={{
|
|
1034
|
+
style={{ bottom: toCssDimension(pageDotsOffset) }}
|
|
1016
1035
|
aria-label="Card pages"
|
|
1017
1036
|
>
|
|
1018
1037
|
{cards.map((_, index) => {
|
|
@@ -1022,7 +1041,7 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
1022
1041
|
|
|
1023
1042
|
return (
|
|
1024
1043
|
<button
|
|
1025
|
-
key={`ocs-page-dot-
|
|
1044
|
+
key={`ocs-page-dot-overlay-${index}`}
|
|
1026
1045
|
type="button"
|
|
1027
1046
|
className="ocs-page-dot"
|
|
1028
1047
|
aria-label={`Go to card ${index + 1}`}
|
|
@@ -1039,7 +1058,62 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
1039
1058
|
})}
|
|
1040
1059
|
</nav>
|
|
1041
1060
|
) : null}
|
|
1042
|
-
|
|
1061
|
+
</div>
|
|
1062
|
+
{showNavigationDots && resolvedPageDotsPosition === "below" ? (
|
|
1063
|
+
<nav
|
|
1064
|
+
className={
|
|
1065
|
+
pageDotsClassName
|
|
1066
|
+
? `ocs-page-dots ocs-page-dots--below ${pageDotsClassName}`
|
|
1067
|
+
: "ocs-page-dots ocs-page-dots--below"
|
|
1068
|
+
}
|
|
1069
|
+
style={{ marginTop: toCssDimension(pageDotsOffset) }}
|
|
1070
|
+
aria-label="Card pages"
|
|
1071
|
+
>
|
|
1072
|
+
{cards.map((_, index) => {
|
|
1073
|
+
const influence = clamp(1 - Math.abs(progress - index), 0, 1);
|
|
1074
|
+
const opacity = 0.25 + influence * 0.75;
|
|
1075
|
+
const scale = 0.9 + influence * 0.22;
|
|
1076
|
+
|
|
1077
|
+
return (
|
|
1078
|
+
<button
|
|
1079
|
+
key={`ocs-page-dot-below-${index}`}
|
|
1080
|
+
type="button"
|
|
1081
|
+
className="ocs-page-dot"
|
|
1082
|
+
aria-label={`Go to card ${index + 1}`}
|
|
1083
|
+
aria-current={influence > 0.98 ? "page" : undefined}
|
|
1084
|
+
onClick={() =>
|
|
1085
|
+
focusCard(index, {
|
|
1086
|
+
behavior: pageDotsBehavior,
|
|
1087
|
+
transitionMode: "swoop",
|
|
1088
|
+
})
|
|
1089
|
+
}
|
|
1090
|
+
style={{ opacity, transform: `scale(${scale})` }}
|
|
1091
|
+
/>
|
|
1092
|
+
);
|
|
1093
|
+
})}
|
|
1094
|
+
</nav>
|
|
1095
|
+
) : null}
|
|
1096
|
+
</>
|
|
1097
|
+
);
|
|
1098
|
+
|
|
1099
|
+
const stageContent = isVerticalTabs ? (
|
|
1100
|
+
<div className="ocs-main-column">{stageAndDots}</div>
|
|
1101
|
+
) : (
|
|
1102
|
+
stageAndDots
|
|
1103
|
+
);
|
|
1104
|
+
|
|
1105
|
+
return (
|
|
1106
|
+
<OverlappingCardsScrollControllerContext.Provider
|
|
1107
|
+
value={controllerContextValue}
|
|
1108
|
+
>
|
|
1109
|
+
<section
|
|
1110
|
+
className={containerClassName}
|
|
1111
|
+
aria-label={ariaLabel}
|
|
1112
|
+
ref={containerRef}
|
|
1113
|
+
>
|
|
1114
|
+
{tabsBeforeStage ? renderTabs() : null}
|
|
1115
|
+
{stageContent}
|
|
1116
|
+
{!tabsBeforeStage ? renderTabs() : null}
|
|
1043
1117
|
</section>
|
|
1044
1118
|
</OverlappingCardsScrollControllerContext.Provider>
|
|
1045
1119
|
);
|