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
|
@@ -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,160 +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
|
-
flexDirection: "column",
|
|
953
|
-
}}
|
|
1008
|
+
<OverlappingCardsScrollCardIndexContext.Provider
|
|
1009
|
+
value={index}
|
|
954
1010
|
>
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
>
|
|
958
|
-
{card}
|
|
959
|
-
</OverlappingCardsScrollCardIndexContext.Provider>
|
|
960
|
-
</div>
|
|
1011
|
+
{card}
|
|
1012
|
+
</OverlappingCardsScrollCardIndexContext.Provider>
|
|
961
1013
|
</div>
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
|
|
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
|
+
/>
|
|
973
1025
|
</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
1026
|
</div>
|
|
1009
|
-
{showNavigationDots && resolvedPageDotsPosition === "
|
|
1027
|
+
{showNavigationDots && resolvedPageDotsPosition === "overlay" ? (
|
|
1010
1028
|
<nav
|
|
1011
1029
|
className={
|
|
1012
1030
|
pageDotsClassName
|
|
1013
|
-
? `ocs-page-dots ocs-page-dots--
|
|
1014
|
-
: "ocs-page-dots ocs-page-dots--
|
|
1031
|
+
? `ocs-page-dots ocs-page-dots--overlay ${pageDotsClassName}`
|
|
1032
|
+
: "ocs-page-dots ocs-page-dots--overlay"
|
|
1015
1033
|
}
|
|
1016
|
-
style={{
|
|
1034
|
+
style={{ bottom: toCssDimension(pageDotsOffset) }}
|
|
1017
1035
|
aria-label="Card pages"
|
|
1018
1036
|
>
|
|
1019
1037
|
{cards.map((_, index) => {
|
|
@@ -1023,7 +1041,7 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
1023
1041
|
|
|
1024
1042
|
return (
|
|
1025
1043
|
<button
|
|
1026
|
-
key={`ocs-page-dot-
|
|
1044
|
+
key={`ocs-page-dot-overlay-${index}`}
|
|
1027
1045
|
type="button"
|
|
1028
1046
|
className="ocs-page-dot"
|
|
1029
1047
|
aria-label={`Go to card ${index + 1}`}
|
|
@@ -1040,7 +1058,62 @@ export function OverlappingCardsScroll(props: OverlappingCardsScrollProps) {
|
|
|
1040
1058
|
})}
|
|
1041
1059
|
</nav>
|
|
1042
1060
|
) : null}
|
|
1043
|
-
|
|
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}
|
|
1044
1117
|
</section>
|
|
1045
1118
|
</OverlappingCardsScrollControllerContext.Provider>
|
|
1046
1119
|
);
|