aplance-ui 1.2.2 → 1.3.0
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/package.json +1 -1
- package/registry/components/primitives/modal/modal.tsx +16 -10
- package/registry/components/primitives/popover/popover-context.ts +1 -0
- package/registry/components/primitives/popover/popover.tsx +7 -2
- package/registry/components/primitives/slider/slider.module.css +62 -5
- package/registry/components/primitives/slider/slider.tsx +192 -34
- package/registry/components/primitives/slider/types.ts +56 -3
- package/registry/components/primitives/slider/utils.ts +9 -0
- package/registry/components/ui/data-table/data-table.module.css +99 -50
- package/registry/components/ui/data-table/data-table.tsx +110 -104
- package/registry/components/ui/input-fields/custom-select/custom-select.module.css +21 -1
- package/registry/components/ui/input-fields/custom-select/custom-select.tsx +29 -14
- package/registry/components/ui/input-fields/input/input.tsx +80 -69
- package/registry/components/ui/tabs/tabs.module.css +22 -8
- package/registry/components/ui/tabs/tabs.tsx +5 -1
- package/registry/hooks/useHasMounted.ts +22 -0
- package/registry/registry.json +20 -3
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
useRef,
|
|
11
11
|
useState,
|
|
12
12
|
type CSSProperties,
|
|
13
|
+
type MouseEvent as ReactMouseEvent,
|
|
13
14
|
type PointerEvent as ReactPointerEvent,
|
|
14
15
|
type ReactNode,
|
|
15
16
|
type TransitionEvent as ReactTransitionEvent,
|
|
@@ -21,11 +22,16 @@ import { useResizeObserver } from "../../../hooks/useResizeObserver";
|
|
|
21
22
|
import { SliderContext, useSlider, type SliderApi } from "./slider-context";
|
|
22
23
|
import type {
|
|
23
24
|
SliderAutoplayOptions,
|
|
25
|
+
SliderArrowProps,
|
|
26
|
+
SliderDotsProps,
|
|
27
|
+
SliderIndicatorProps,
|
|
24
28
|
SliderNavigationOptions,
|
|
29
|
+
SliderPaginationItems,
|
|
25
30
|
SliderPaginationOptions,
|
|
26
31
|
SliderPaginationType,
|
|
27
32
|
SliderProps,
|
|
28
33
|
SliderSlideState,
|
|
34
|
+
SliderVariantOptions,
|
|
29
35
|
} from "./types";
|
|
30
36
|
import {
|
|
31
37
|
clamp,
|
|
@@ -41,13 +47,18 @@ export {
|
|
|
41
47
|
sliderAlignments,
|
|
42
48
|
sliderEffects,
|
|
43
49
|
sliderPaginationTypes,
|
|
50
|
+
sliderVariants,
|
|
44
51
|
} from "./types";
|
|
45
52
|
export type {
|
|
46
53
|
SliderAlign,
|
|
47
54
|
SliderAutoplayOptions,
|
|
55
|
+
SliderArrowProps,
|
|
56
|
+
SliderDotsProps,
|
|
48
57
|
SliderEffect,
|
|
49
58
|
SliderHandle,
|
|
59
|
+
SliderIndicatorProps,
|
|
50
60
|
SliderNavigationOptions,
|
|
61
|
+
SliderPaginationItems,
|
|
51
62
|
SliderPaginationOptions,
|
|
52
63
|
SliderPaginationType,
|
|
53
64
|
SliderProps,
|
|
@@ -55,17 +66,65 @@ export type {
|
|
|
55
66
|
SliderResponsiveOptions,
|
|
56
67
|
SliderSlideState,
|
|
57
68
|
SliderSlidesPerView,
|
|
69
|
+
SliderVariant,
|
|
70
|
+
SliderVariantOptions,
|
|
58
71
|
} from "./types";
|
|
59
72
|
|
|
73
|
+
// Swallow the browser click after a swipe so linked slides stay draggable.
|
|
74
|
+
const DRAG_CLICK_TOLERANCE = 6;
|
|
75
|
+
const EMPTY_VARIANT_OPTIONS: SliderVariantOptions = {};
|
|
76
|
+
|
|
77
|
+
type SliderCssProperties = CSSProperties &
|
|
78
|
+
Record<`--slider-${string}`, string | number | undefined>;
|
|
79
|
+
|
|
80
|
+
const resolveVariantOptions = (
|
|
81
|
+
variant: "default" | "cards" | "coverflow",
|
|
82
|
+
options: SliderVariantOptions,
|
|
83
|
+
): Required<SliderVariantOptions> => {
|
|
84
|
+
const offset = typeof options.offset === "number" && Number.isFinite(options.offset)
|
|
85
|
+
? Math.max(0, options.offset)
|
|
86
|
+
: variant === "cards"
|
|
87
|
+
? 6
|
|
88
|
+
: 0;
|
|
89
|
+
const scaleStep = typeof options.scaleStep === "number" && Number.isFinite(options.scaleStep)
|
|
90
|
+
? clamp(options.scaleStep, 0, 1)
|
|
91
|
+
: variant === "cards"
|
|
92
|
+
? 0.07
|
|
93
|
+
: 0.08;
|
|
94
|
+
const opacityStep = typeof options.opacityStep === "number" && Number.isFinite(options.opacityStep)
|
|
95
|
+
? clamp(options.opacityStep, 0, 1)
|
|
96
|
+
: variant === "cards"
|
|
97
|
+
? 0.16
|
|
98
|
+
: 0.2;
|
|
99
|
+
const rotate = typeof options.rotate === "number" && Number.isFinite(options.rotate)
|
|
100
|
+
? clamp(options.rotate, 0, 90)
|
|
101
|
+
: 38;
|
|
102
|
+
const depth = typeof options.depth === "number" && Number.isFinite(options.depth)
|
|
103
|
+
? Math.max(0, options.depth)
|
|
104
|
+
: 96;
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
offset,
|
|
108
|
+
scaleStep,
|
|
109
|
+
opacityStep,
|
|
110
|
+
rotate,
|
|
111
|
+
depth,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
|
|
60
115
|
// ─── Slider ───────────────────────────────────────────────────────────────────
|
|
61
116
|
|
|
62
117
|
export const Slider = ({
|
|
63
118
|
children,
|
|
64
119
|
className,
|
|
120
|
+
viewportClassName,
|
|
121
|
+
trackClassName,
|
|
65
122
|
style,
|
|
66
123
|
ref,
|
|
67
124
|
ariaLabel = "Slider",
|
|
68
125
|
effect: effectProp = "slide",
|
|
126
|
+
variant: variantProp = "default",
|
|
127
|
+
variantOptions: variantOptionsProp = EMPTY_VARIANT_OPTIONS,
|
|
69
128
|
align: alignProp = "start",
|
|
70
129
|
centered: centeredProp = false,
|
|
71
130
|
slidesPerView: slidesPerViewProp = 1,
|
|
@@ -82,7 +141,8 @@ export const Slider = ({
|
|
|
82
141
|
easing = "cubic-bezier(0.4, 0, 0.2, 1)",
|
|
83
142
|
draggable = true,
|
|
84
143
|
swipeThreshold = 30,
|
|
85
|
-
|
|
144
|
+
// Linked slides stay draggable. Click suppression distinguishes swipes from taps.
|
|
145
|
+
dragIgnoreSelector = "button, input, textarea, select, summary, [role='button'], [data-slider-no-drag]",
|
|
86
146
|
keyboard = false,
|
|
87
147
|
mousewheel = false,
|
|
88
148
|
autoHeight = false,
|
|
@@ -166,6 +226,8 @@ export const Slider = ({
|
|
|
166
226
|
centered: centeredProp,
|
|
167
227
|
align: alignProp,
|
|
168
228
|
effect: effectProp,
|
|
229
|
+
variant: variantProp,
|
|
230
|
+
variantOptions: variantOptionsProp,
|
|
169
231
|
},
|
|
170
232
|
responsive,
|
|
171
233
|
),
|
|
@@ -177,6 +239,8 @@ export const Slider = ({
|
|
|
177
239
|
slidesPerGroupProp,
|
|
178
240
|
slidesPerViewProp,
|
|
179
241
|
spaceBetweenProp,
|
|
242
|
+
variantProp,
|
|
243
|
+
variantOptionsProp,
|
|
180
244
|
viewportWidth,
|
|
181
245
|
],
|
|
182
246
|
);
|
|
@@ -200,6 +264,8 @@ export const Slider = ({
|
|
|
200
264
|
const align = responsiveOptions.centered ? "center" : responsiveOptions.align;
|
|
201
265
|
const effect = responsiveOptions.effect;
|
|
202
266
|
const effectMode = effect;
|
|
267
|
+
const variant = effectMode === "slide" ? responsiveOptions.variant : "default";
|
|
268
|
+
const variantOptions = resolveVariantOptions(variant, responsiveOptions.variantOptions);
|
|
203
269
|
const canLoop = loop && count > 1 && effectMode === "slide";
|
|
204
270
|
const baseMaxIndex =
|
|
205
271
|
effectMode === "slide" ? getSnapMax(count, slidesPerView) : Math.max(0, count - 1);
|
|
@@ -255,6 +321,10 @@ export const Slider = ({
|
|
|
255
321
|
}
|
|
256
322
|
}, [count, isControlled, loop, maxIndex]);
|
|
257
323
|
|
|
324
|
+
useLayoutEffect(() => {
|
|
325
|
+
if (autoHeight) measure();
|
|
326
|
+
}, [autoHeight, logicalIndex, measure]);
|
|
327
|
+
|
|
258
328
|
// Clone N slides on each side so dragging/grouping past the edge always
|
|
259
329
|
// has real DOM under the cursor. `+1` is a buffer for partial-view layouts;
|
|
260
330
|
// `slidesPerView=auto` is unknowable up-front so we clone the full set.
|
|
@@ -294,6 +364,13 @@ export const Slider = ({
|
|
|
294
364
|
const [isFocusWithin, setIsFocusWithin] = useState(false);
|
|
295
365
|
const [autoplayStopped, setAutoplayStopped] = useState(false);
|
|
296
366
|
const wheelLockRef = useRef(false);
|
|
367
|
+
const suppressClickRef = useRef(false);
|
|
368
|
+
// True once a pointer interaction has crossed the drag threshold and become a
|
|
369
|
+
// real drag (so we capture the pointer + move the track). A plain tap never
|
|
370
|
+
// flips this, so it never captures the pointer - which is what lets a card's
|
|
371
|
+
// <a> receive the click (a captured pointer makes the browser dispatch the
|
|
372
|
+
// `click` to the captured viewport, not the link, so navigation silently dies).
|
|
373
|
+
const dragActiveRef = useRef(false);
|
|
297
374
|
const layoutSignatureRef = useRef("");
|
|
298
375
|
const dragStartRef = useRef<{
|
|
299
376
|
x: number;
|
|
@@ -310,6 +387,7 @@ export const Slider = ({
|
|
|
310
387
|
slidesPerGroup,
|
|
311
388
|
slidesPerView,
|
|
312
389
|
spaceBetween,
|
|
390
|
+
variant,
|
|
313
391
|
viewportWidth,
|
|
314
392
|
].join("|");
|
|
315
393
|
|
|
@@ -387,6 +465,9 @@ export const Slider = ({
|
|
|
387
465
|
? clamp(baseOffset - alignmentOffset, 0, maxScroll)
|
|
388
466
|
: baseOffset - alignmentOffset;
|
|
389
467
|
const translate = -targetOffset + dragOffset;
|
|
468
|
+
const visualPosition = visualIndex - dragOffset / Math.max(1, averageStride);
|
|
469
|
+
const visibleStart = targetOffset - dragOffset;
|
|
470
|
+
const visibleEnd = visibleStart + viewportWidth;
|
|
390
471
|
const progress = snapCount <= 1 ? 1 : clamp(logicalIndex / (snapCount - 1), 0, 1);
|
|
391
472
|
const canNavigateLoop = loop && count > 1;
|
|
392
473
|
const isFirst = !canNavigateLoop && logicalIndex <= 0;
|
|
@@ -636,6 +717,8 @@ export const Slider = ({
|
|
|
636
717
|
// ─── Pointer / keyboard / wheel ─────────────────────────────────────────────
|
|
637
718
|
|
|
638
719
|
const handlePointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
|
|
720
|
+
suppressClickRef.current = false;
|
|
721
|
+
dragActiveRef.current = false;
|
|
639
722
|
if (
|
|
640
723
|
!draggable ||
|
|
641
724
|
count <= 1 ||
|
|
@@ -647,6 +730,11 @@ export const Slider = ({
|
|
|
647
730
|
if (event.pointerType === "mouse" && event.button !== 0) return;
|
|
648
731
|
|
|
649
732
|
markInteraction();
|
|
733
|
+
// Record the START point only. We do NOT capture the pointer or enter the
|
|
734
|
+
// dragging state yet: a plain click must stay a plain click so the card's
|
|
735
|
+
// <a> receives it (a captured pointer makes the browser deliver the `click`
|
|
736
|
+
// to the captured viewport, not the link). The drag is promoted in
|
|
737
|
+
// `handlePointerMove` once the pointer travels past the threshold.
|
|
650
738
|
dragStartRef.current = {
|
|
651
739
|
x: event.clientX,
|
|
652
740
|
y: event.clientY,
|
|
@@ -654,9 +742,6 @@ export const Slider = ({
|
|
|
654
742
|
logicalIndex,
|
|
655
743
|
};
|
|
656
744
|
verticalLockRef.current = false;
|
|
657
|
-
setIsDragging(true);
|
|
658
|
-
setIsAnimating(false);
|
|
659
|
-
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
660
745
|
};
|
|
661
746
|
|
|
662
747
|
const handlePointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {
|
|
@@ -666,17 +751,31 @@ export const Slider = ({
|
|
|
666
751
|
const dx = event.clientX - start.x;
|
|
667
752
|
const dy = event.clientY - start.y;
|
|
668
753
|
|
|
669
|
-
|
|
754
|
+
// Before the drag is committed, a clearly-vertical touch is a page scroll -
|
|
755
|
+
// abandon the gesture so the page scrolls normally.
|
|
756
|
+
if (
|
|
757
|
+
event.pointerType !== "mouse" &&
|
|
758
|
+
!verticalLockRef.current &&
|
|
759
|
+
!dragActiveRef.current
|
|
760
|
+
) {
|
|
670
761
|
if (Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > 8) {
|
|
671
762
|
verticalLockRef.current = true;
|
|
672
763
|
dragStartRef.current = null;
|
|
673
|
-
setIsDragging(false);
|
|
674
|
-
setDragOffset(0);
|
|
675
|
-
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
676
764
|
return;
|
|
677
765
|
}
|
|
678
766
|
}
|
|
679
767
|
|
|
768
|
+
// Promote to a real drag only once past the threshold. Until then this is
|
|
769
|
+
// still a potential tap, so we neither capture the pointer nor move the
|
|
770
|
+
// track - which is what keeps link clicks working.
|
|
771
|
+
if (!dragActiveRef.current) {
|
|
772
|
+
if (Math.abs(dx) <= DRAG_CLICK_TOLERANCE) return;
|
|
773
|
+
dragActiveRef.current = true;
|
|
774
|
+
setIsDragging(true);
|
|
775
|
+
setIsAnimating(false);
|
|
776
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
777
|
+
}
|
|
778
|
+
|
|
680
779
|
// Rubber-band at non-loop edges: dampen drag so users feel the bound
|
|
681
780
|
// without breaking the snap-back on release.
|
|
682
781
|
const shouldRubberBand =
|
|
@@ -690,9 +789,14 @@ export const Slider = ({
|
|
|
690
789
|
|
|
691
790
|
const dx = clientX - start.x;
|
|
692
791
|
dragStartRef.current = null;
|
|
792
|
+
dragActiveRef.current = false;
|
|
693
793
|
setIsDragging(false);
|
|
694
794
|
setDragOffset(0);
|
|
695
795
|
|
|
796
|
+
if (Math.abs(dx) > DRAG_CLICK_TOLERANCE) {
|
|
797
|
+
suppressClickRef.current = true;
|
|
798
|
+
}
|
|
799
|
+
|
|
696
800
|
if (Math.abs(dx) < resolvedSwipeThreshold) return;
|
|
697
801
|
|
|
698
802
|
const steps = Math.max(1, Math.round(Math.abs(dx) / Math.max(1, averageStride)));
|
|
@@ -700,16 +804,31 @@ export const Slider = ({
|
|
|
700
804
|
else prev(steps);
|
|
701
805
|
};
|
|
702
806
|
|
|
807
|
+
const releaseCapture = (event: ReactPointerEvent<HTMLDivElement>) => {
|
|
808
|
+
if (event.currentTarget.hasPointerCapture?.(event.pointerId)) {
|
|
809
|
+
event.currentTarget.releasePointerCapture?.(event.pointerId);
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
|
|
703
813
|
const handlePointerUp = (event: ReactPointerEvent<HTMLDivElement>) => {
|
|
704
814
|
finishDrag(event.clientX);
|
|
705
|
-
|
|
815
|
+
releaseCapture(event);
|
|
706
816
|
};
|
|
707
817
|
|
|
708
818
|
const handlePointerCancel = (event: ReactPointerEvent<HTMLDivElement>) => {
|
|
709
819
|
dragStartRef.current = null;
|
|
820
|
+
dragActiveRef.current = false;
|
|
710
821
|
setIsDragging(false);
|
|
711
822
|
setDragOffset(0);
|
|
712
|
-
|
|
823
|
+
releaseCapture(event);
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
const handleClickCapture = (event: ReactMouseEvent<HTMLDivElement>) => {
|
|
827
|
+
if (suppressClickRef.current) {
|
|
828
|
+
event.preventDefault();
|
|
829
|
+
event.stopPropagation();
|
|
830
|
+
suppressClickRef.current = false;
|
|
831
|
+
}
|
|
713
832
|
};
|
|
714
833
|
|
|
715
834
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
@@ -845,9 +964,15 @@ export const Slider = ({
|
|
|
845
964
|
className={clsx(
|
|
846
965
|
s.container,
|
|
847
966
|
effectMode !== "slide" && s.container_effect,
|
|
967
|
+
isDragging && s.container_dragging,
|
|
968
|
+
(!isMotionReady || transitionOff) && s.container_no_transition,
|
|
848
969
|
className,
|
|
849
970
|
)}
|
|
850
|
-
style={
|
|
971
|
+
style={{
|
|
972
|
+
"--slider-transition-duration": `${transitionDuration}ms`,
|
|
973
|
+
"--slider-transition-easing": easing,
|
|
974
|
+
...style,
|
|
975
|
+
} as SliderCssProperties}
|
|
851
976
|
role="region"
|
|
852
977
|
aria-roledescription="carousel"
|
|
853
978
|
aria-label={ariaLabel}
|
|
@@ -876,7 +1001,11 @@ export const Slider = ({
|
|
|
876
1001
|
>
|
|
877
1002
|
<div
|
|
878
1003
|
ref={viewportRef}
|
|
879
|
-
className={
|
|
1004
|
+
className={clsx(
|
|
1005
|
+
s.viewport,
|
|
1006
|
+
variant === "coverflow" && s.viewport_coverflow,
|
|
1007
|
+
viewportClassName,
|
|
1008
|
+
)}
|
|
880
1009
|
style={{
|
|
881
1010
|
...viewportStyle,
|
|
882
1011
|
visibility: hasMeasured ? undefined : "hidden",
|
|
@@ -887,6 +1016,7 @@ export const Slider = ({
|
|
|
887
1016
|
onPointerMove={handlePointerMove}
|
|
888
1017
|
onPointerUp={handlePointerUp}
|
|
889
1018
|
onPointerCancel={handlePointerCancel}
|
|
1019
|
+
onClickCapture={handleClickCapture}
|
|
890
1020
|
onDragStart={(event) => event.preventDefault()}
|
|
891
1021
|
>
|
|
892
1022
|
<div
|
|
@@ -895,6 +1025,9 @@ export const Slider = ({
|
|
|
895
1025
|
s.track,
|
|
896
1026
|
effectMode === "fade" && s.track_fade,
|
|
897
1027
|
effectMode === "scale" && s.track_scale,
|
|
1028
|
+
effectMode === "flip" && s.track_flip,
|
|
1029
|
+
variant === "coverflow" && s.track_coverflow,
|
|
1030
|
+
trackClassName,
|
|
898
1031
|
)}
|
|
899
1032
|
style={trackStyle}
|
|
900
1033
|
onTransitionEnd={handleTrackTransitionEnd}
|
|
@@ -907,13 +1040,21 @@ export const Slider = ({
|
|
|
907
1040
|
canLoop && (renderedIndex < cloneCount || renderedIndex >= cloneCount + count);
|
|
908
1041
|
const visibleCount =
|
|
909
1042
|
slidesPerView === "auto" ? 1 : Math.ceil(slidesPerView);
|
|
1043
|
+
const slideOffset = slideOffsets[renderedIndex];
|
|
1044
|
+
const slideSize = slideSizes[renderedIndex];
|
|
910
1045
|
const isActive =
|
|
911
1046
|
realIndex === logicalIndex &&
|
|
912
1047
|
(effectMode === "slide" ? renderedIndex === visualIndex : !isClone);
|
|
913
1048
|
const isVisible = effectMode !== "slide"
|
|
914
1049
|
? realIndex === logicalIndex
|
|
915
|
-
:
|
|
916
|
-
|
|
1050
|
+
: hasMeasured &&
|
|
1051
|
+
viewportWidth > 0 &&
|
|
1052
|
+
slideOffset !== undefined &&
|
|
1053
|
+
slideSize !== undefined
|
|
1054
|
+
? slideOffset + slideSize > visibleStart &&
|
|
1055
|
+
slideOffset < visibleEnd
|
|
1056
|
+
: renderedIndex >= visualIndex &&
|
|
1057
|
+
renderedIndex < visualIndex + visibleCount;
|
|
917
1058
|
const state: SliderSlideState = {
|
|
918
1059
|
index: realIndex,
|
|
919
1060
|
renderedIndex,
|
|
@@ -921,6 +1062,24 @@ export const Slider = ({
|
|
|
921
1062
|
isVisible,
|
|
922
1063
|
isClone,
|
|
923
1064
|
};
|
|
1065
|
+
const slideDistance = clamp(renderedIndex - visualPosition, -3, 3);
|
|
1066
|
+
const slideAbsDistance = Math.abs(slideDistance);
|
|
1067
|
+
const variantStyle = variant === "default"
|
|
1068
|
+
? undefined
|
|
1069
|
+
: {
|
|
1070
|
+
"--slider-slide-offset": `${slideAbsDistance * variantOptions.offset}px`,
|
|
1071
|
+
"--slider-slide-scale": Math.max(
|
|
1072
|
+
variant === "cards" ? 0.86 : 0.82,
|
|
1073
|
+
1 - slideAbsDistance * variantOptions.scaleStep,
|
|
1074
|
+
),
|
|
1075
|
+
"--slider-slide-depth": `${slideAbsDistance * variantOptions.depth * -1}px`,
|
|
1076
|
+
"--slider-slide-rotate": `${slideDistance * variantOptions.rotate * -1}deg`,
|
|
1077
|
+
opacity: Math.max(
|
|
1078
|
+
variant === "cards" ? 0.64 : 0.5,
|
|
1079
|
+
1 - slideAbsDistance * variantOptions.opacityStep,
|
|
1080
|
+
),
|
|
1081
|
+
zIndex: Math.max(0, 10 - Math.round(slideAbsDistance)),
|
|
1082
|
+
} as SliderCssProperties;
|
|
924
1083
|
|
|
925
1084
|
return (
|
|
926
1085
|
<div
|
|
@@ -936,11 +1095,22 @@ export const Slider = ({
|
|
|
936
1095
|
className={clsx(
|
|
937
1096
|
s.slide,
|
|
938
1097
|
effectMode !== "slide" && s.slide_overlay,
|
|
1098
|
+
variant === "cards" && s.slide_cards,
|
|
1099
|
+
variant === "coverflow" && s.slide_coverflow,
|
|
939
1100
|
isActive && s.slide_active,
|
|
940
1101
|
isVisible && s.slide_visible,
|
|
941
1102
|
)}
|
|
942
|
-
style={slideStyle}
|
|
943
|
-
|
|
1103
|
+
style={variantStyle ? { ...slideStyle, ...variantStyle } : slideStyle}
|
|
1104
|
+
// Only CLONES are inert + hidden from AT (they're duplicate,
|
|
1105
|
+
// non-interactive copies). REAL slides stay interactive even
|
|
1106
|
+
// when scrolled out of view: they're clipped by the viewport's
|
|
1107
|
+
// `overflow`, so an off-screen card can't be clicked anyway,
|
|
1108
|
+
// and gating `inert` on the measurement-dependent `isVisible`
|
|
1109
|
+
// flag would intermittently make an on-screen card
|
|
1110
|
+
// non-clickable (the swipe/measure race the user hit - the
|
|
1111
|
+
// link shows on hover, but the click lands while inert).
|
|
1112
|
+
aria-hidden={isClone || undefined}
|
|
1113
|
+
inert={isClone ? true : undefined}
|
|
944
1114
|
data-active={isActive ? "true" : undefined}
|
|
945
1115
|
data-visible={isVisible ? "true" : undefined}
|
|
946
1116
|
data-clone={isClone ? "true" : undefined}
|
|
@@ -1022,7 +1192,7 @@ const SliderNavButton = ({
|
|
|
1022
1192
|
type SliderPaginationViewProps = {
|
|
1023
1193
|
type: SliderPaginationType;
|
|
1024
1194
|
clickable: boolean;
|
|
1025
|
-
items:
|
|
1195
|
+
items: SliderPaginationItems;
|
|
1026
1196
|
className?: string;
|
|
1027
1197
|
};
|
|
1028
1198
|
|
|
@@ -1092,11 +1262,7 @@ export const SliderPrev = ({
|
|
|
1092
1262
|
className,
|
|
1093
1263
|
children,
|
|
1094
1264
|
ariaLabel = "Previous slide",
|
|
1095
|
-
}: {
|
|
1096
|
-
className?: string;
|
|
1097
|
-
children?: ReactNode;
|
|
1098
|
-
ariaLabel?: string;
|
|
1099
|
-
}) => {
|
|
1265
|
+
}: SliderArrowProps) => {
|
|
1100
1266
|
const { prev, isFirst } = useSlider();
|
|
1101
1267
|
return (
|
|
1102
1268
|
<button
|
|
@@ -1116,11 +1282,7 @@ export const SliderNext = ({
|
|
|
1116
1282
|
className,
|
|
1117
1283
|
children,
|
|
1118
1284
|
ariaLabel = "Next slide",
|
|
1119
|
-
}: {
|
|
1120
|
-
className?: string;
|
|
1121
|
-
children?: ReactNode;
|
|
1122
|
-
ariaLabel?: string;
|
|
1123
|
-
}) => {
|
|
1285
|
+
}: SliderArrowProps) => {
|
|
1124
1286
|
const { next, isLast } = useSlider();
|
|
1125
1287
|
return (
|
|
1126
1288
|
<button
|
|
@@ -1140,11 +1302,7 @@ export const SliderDots = ({
|
|
|
1140
1302
|
className,
|
|
1141
1303
|
clickable = true,
|
|
1142
1304
|
items = "snaps",
|
|
1143
|
-
}:
|
|
1144
|
-
className?: string;
|
|
1145
|
-
clickable?: boolean;
|
|
1146
|
-
items?: "snaps" | "slides";
|
|
1147
|
-
}) => (
|
|
1305
|
+
}: SliderDotsProps) => (
|
|
1148
1306
|
<SliderPaginationView
|
|
1149
1307
|
type="dots"
|
|
1150
1308
|
clickable={clickable}
|
|
@@ -1153,7 +1311,7 @@ export const SliderDots = ({
|
|
|
1153
1311
|
/>
|
|
1154
1312
|
);
|
|
1155
1313
|
|
|
1156
|
-
export const SliderFraction = ({ className }:
|
|
1314
|
+
export const SliderFraction = ({ className }: SliderIndicatorProps) => (
|
|
1157
1315
|
<SliderPaginationView
|
|
1158
1316
|
type="fraction"
|
|
1159
1317
|
clickable={false}
|
|
@@ -1162,7 +1320,7 @@ export const SliderFraction = ({ className }: { className?: string }) => (
|
|
|
1162
1320
|
/>
|
|
1163
1321
|
);
|
|
1164
1322
|
|
|
1165
|
-
export const SliderProgress = ({ className }:
|
|
1323
|
+
export const SliderProgress = ({ className }: SliderIndicatorProps) => (
|
|
1166
1324
|
<SliderPaginationView
|
|
1167
1325
|
type="progress"
|
|
1168
1326
|
clickable={false}
|
|
@@ -6,17 +6,35 @@ import type {
|
|
|
6
6
|
} from "react";
|
|
7
7
|
import type { SliderApi } from "./slider-context";
|
|
8
8
|
|
|
9
|
-
export const sliderEffects = ["slide", "fade", "scale"] as const;
|
|
9
|
+
export const sliderEffects = ["slide", "fade", "scale", "flip"] as const;
|
|
10
10
|
export type SliderEffect = (typeof sliderEffects)[number];
|
|
11
11
|
|
|
12
|
+
export const sliderVariants = ["default", "cards", "coverflow"] as const;
|
|
13
|
+
export type SliderVariant = (typeof sliderVariants)[number];
|
|
14
|
+
|
|
12
15
|
export const sliderAlignments = ["start", "center", "end"] as const;
|
|
13
16
|
export type SliderAlign = (typeof sliderAlignments)[number];
|
|
14
17
|
|
|
15
18
|
export const sliderPaginationTypes = ["dots", "fraction", "progress"] as const;
|
|
16
19
|
export type SliderPaginationType = (typeof sliderPaginationTypes)[number];
|
|
17
20
|
|
|
21
|
+
export type SliderPaginationItems = "snaps" | "slides";
|
|
22
|
+
|
|
18
23
|
export type SliderSlidesPerView = number | "auto";
|
|
19
24
|
|
|
25
|
+
export type SliderVariantOptions = {
|
|
26
|
+
/** Vertical offset per slide distance in px. */
|
|
27
|
+
offset?: number;
|
|
28
|
+
/** Scale reduction per slide distance. */
|
|
29
|
+
scaleStep?: number;
|
|
30
|
+
/** Opacity reduction per slide distance. */
|
|
31
|
+
opacityStep?: number;
|
|
32
|
+
/** Coverflow rotation per slide distance in degrees. */
|
|
33
|
+
rotate?: number;
|
|
34
|
+
/** Coverflow depth reduction per slide distance in px. */
|
|
35
|
+
depth?: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
20
38
|
export type SliderResponsiveOptions = {
|
|
21
39
|
slidesPerView?: SliderSlidesPerView;
|
|
22
40
|
slidesPerGroup?: number;
|
|
@@ -24,6 +42,8 @@ export type SliderResponsiveOptions = {
|
|
|
24
42
|
centered?: boolean;
|
|
25
43
|
align?: SliderAlign;
|
|
26
44
|
effect?: SliderEffect;
|
|
45
|
+
variant?: SliderVariant;
|
|
46
|
+
variantOptions?: SliderVariantOptions;
|
|
27
47
|
};
|
|
28
48
|
|
|
29
49
|
/** Responsive overrides keyed by minimum viewport width in px. */
|
|
@@ -55,7 +75,23 @@ export type SliderPaginationOptions = {
|
|
|
55
75
|
/** When true, dots/fraction items are clickable. */
|
|
56
76
|
clickable?: boolean;
|
|
57
77
|
/** Paginate by snap pages or every real slide. */
|
|
58
|
-
items?:
|
|
78
|
+
items?: SliderPaginationItems;
|
|
79
|
+
className?: string;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type SliderArrowProps = {
|
|
83
|
+
className?: string;
|
|
84
|
+
children?: ReactNode;
|
|
85
|
+
ariaLabel?: string;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type SliderDotsProps = {
|
|
89
|
+
className?: string;
|
|
90
|
+
clickable?: boolean;
|
|
91
|
+
items?: SliderPaginationItems;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type SliderIndicatorProps = {
|
|
59
95
|
className?: string;
|
|
60
96
|
};
|
|
61
97
|
|
|
@@ -81,12 +117,29 @@ export type SliderHandle = {
|
|
|
81
117
|
export type SliderProps = {
|
|
82
118
|
children: ReactNode;
|
|
83
119
|
className?: string;
|
|
120
|
+
/** Class merged onto the inner clipping viewport. Use it to add vertical
|
|
121
|
+
* breathing room (e.g. `padding-block`) when slides carry borders /
|
|
122
|
+
* shadows / hover states that would otherwise be shaved by the viewport's
|
|
123
|
+
* `overflow: hidden`. Keep it to vertical padding only - horizontal padding
|
|
124
|
+
* would skew the slide-width measurement. */
|
|
125
|
+
viewportClassName?: string;
|
|
126
|
+
/** Class merged onto the moving track (the flex strip that holds the slides).
|
|
127
|
+
* Use it when the slider must fill a definite-height parent (e.g. a
|
|
128
|
+
* full-screen lightbox): set the track to `height: 100%` so it - and the
|
|
129
|
+
* stretch-aligned slides - inherit the viewport height instead of collapsing
|
|
130
|
+
* to slide content. */
|
|
131
|
+
trackClassName?: string;
|
|
84
132
|
style?: CSSProperties;
|
|
85
133
|
ref?: Ref<SliderHandle>;
|
|
86
134
|
ariaLabel?: string;
|
|
87
135
|
|
|
88
|
-
/** Slide transition effect. `fade` and `
|
|
136
|
+
/** Slide transition effect. `fade`, `scale`, and `flip` are single-view effects. */
|
|
89
137
|
effect?: SliderEffect;
|
|
138
|
+
/** Visual treatment for slide layouts. `cards` and `coverflow` enhance the
|
|
139
|
+
* standard slide effect without changing navigation behavior. */
|
|
140
|
+
variant?: SliderVariant;
|
|
141
|
+
/** Optional tuning for `cards` and `coverflow` presentation variants. */
|
|
142
|
+
variantOptions?: SliderVariantOptions;
|
|
90
143
|
/** Alignment of the active slide inside the viewport. */
|
|
91
144
|
align?: SliderAlign;
|
|
92
145
|
/** Center active slides. Alias for align="center". */
|
|
@@ -73,6 +73,15 @@ export const resolveResponsive = (
|
|
|
73
73
|
if (options.effect !== undefined) {
|
|
74
74
|
active.effect = options.effect;
|
|
75
75
|
}
|
|
76
|
+
if (options.variant !== undefined) {
|
|
77
|
+
active.variant = options.variant;
|
|
78
|
+
}
|
|
79
|
+
if (options.variantOptions !== undefined) {
|
|
80
|
+
active.variantOptions = {
|
|
81
|
+
...active.variantOptions,
|
|
82
|
+
...options.variantOptions,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
return active;
|