@cntyclub/ui-react 0.10.4 → 0.10.6
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.d.ts +10 -1
- package/dist/index.js +42 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ui/avatar-group.tsx +3 -1
- package/src/components/ui/smooth-scroll.tsx +74 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cntyclub/ui-react",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.6",
|
|
4
4
|
"description": "React component library for the Country Club UI Kit — Base UI primitives styled with the Country Club design system (Tailwind CSS v4)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -81,7 +81,9 @@ function AvatarGroup({
|
|
|
81
81
|
aria-label={`${overflow} more`}
|
|
82
82
|
className={cn(
|
|
83
83
|
avatarVariants({ size }),
|
|
84
|
-
|
|
84
|
+
// Solid neutral fill so the chip reads as a distinct surface in both
|
|
85
|
+
// themes — the `bg-muted` token is only 4% opacity (near-transparent).
|
|
86
|
+
"bg-stone-200 font-medium text-stone-600 dark:bg-stone-700 dark:text-stone-200",
|
|
85
87
|
)}
|
|
86
88
|
data-slot="avatar-group-overflow"
|
|
87
89
|
>
|
|
@@ -19,7 +19,16 @@ export interface SmoothWheelOptions {
|
|
|
19
19
|
* Eased, momentum-style wheel scrolling for any scrollable element. Instead of
|
|
20
20
|
* jumping `scrollTop`/`scrollLeft` by the raw wheel delta (which feels fast and
|
|
21
21
|
* abrupt), it animates toward an accumulated target with `requestAnimationFrame`.
|
|
22
|
-
*
|
|
22
|
+
*
|
|
23
|
+
* It only smooths genuine mouse wheels. Trackpads already scroll smoothly with
|
|
24
|
+
* OS-level inertia, so layering our own easing on top makes it fight the native
|
|
25
|
+
* momentum — the container sticks, or snaps back to where the animation was
|
|
26
|
+
* still heading. In the default vertical mode a trackpad therefore keeps native
|
|
27
|
+
* behaviour; in horizontal mode we still translate a vertical mouse wheel into
|
|
28
|
+
* horizontal scrolling (the whole point of the mode) but let a real horizontal
|
|
29
|
+
* trackpad swipe scroll natively. Every time control returns to native
|
|
30
|
+
* scrolling — trackpad, or an edge — the in-flight animation is cancelled and
|
|
31
|
+
* the target resynced so it can never pull the position back.
|
|
23
32
|
*/
|
|
24
33
|
export function useSmoothWheel(
|
|
25
34
|
ref: React.RefObject<HTMLElement | null>,
|
|
@@ -47,6 +56,17 @@ export function useSmoothWheel(
|
|
|
47
56
|
let target = el[axisProp];
|
|
48
57
|
let raf = 0;
|
|
49
58
|
let animating = false;
|
|
59
|
+
let kind: "mouse" | "trackpad" | null = null;
|
|
60
|
+
let lastTs = 0;
|
|
61
|
+
|
|
62
|
+
// Cancel the animation and resync the target to the real position, so any
|
|
63
|
+
// native scrolling that follows is never yanked back to a stale target.
|
|
64
|
+
const releaseToNative = () => {
|
|
65
|
+
if (raf) cancelAnimationFrame(raf);
|
|
66
|
+
raf = 0;
|
|
67
|
+
animating = false;
|
|
68
|
+
target = el[axisProp];
|
|
69
|
+
};
|
|
50
70
|
|
|
51
71
|
const tick = () => {
|
|
52
72
|
const max = el[sizeProp] - el[clientProp];
|
|
@@ -55,6 +75,7 @@ export function useSmoothWheel(
|
|
|
55
75
|
const diff = target - current;
|
|
56
76
|
if (Math.abs(diff) < 0.5) {
|
|
57
77
|
el[axisProp] = target;
|
|
78
|
+
raf = 0;
|
|
58
79
|
animating = false;
|
|
59
80
|
return;
|
|
60
81
|
}
|
|
@@ -62,27 +83,69 @@ export function useSmoothWheel(
|
|
|
62
83
|
raf = requestAnimationFrame(tick);
|
|
63
84
|
};
|
|
64
85
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
// Strong per-event trackpad signals: fractional deltas, a horizontal
|
|
87
|
+
// component (mice almost never emit deltaX), or small pixel steps.
|
|
88
|
+
const looksLikeTrackpad = (e: WheelEvent) =>
|
|
89
|
+
e.deltaMode === 0 &&
|
|
90
|
+
(!Number.isInteger(e.deltaY) ||
|
|
91
|
+
e.deltaX !== 0 ||
|
|
92
|
+
Math.abs(e.deltaY) < 50);
|
|
93
|
+
|
|
94
|
+
// Sticky, gesture-scoped classification: decide once per burst of events
|
|
95
|
+
// (< 200ms apart) so a hard trackpad flick whose mid-gesture deltas look
|
|
96
|
+
// mouse-like stays classified as a trackpad the whole way through.
|
|
97
|
+
const classify = (e: WheelEvent, now: number): "mouse" | "trackpad" => {
|
|
98
|
+
if (kind === null || now - lastTs > 200) {
|
|
99
|
+
kind = looksLikeTrackpad(e) ? "trackpad" : "mouse";
|
|
100
|
+
} else if (
|
|
101
|
+
kind === "mouse" &&
|
|
102
|
+
(e.deltaX !== 0 || !Number.isInteger(e.deltaY))
|
|
103
|
+
) {
|
|
104
|
+
kind = "trackpad";
|
|
105
|
+
}
|
|
106
|
+
return kind;
|
|
72
107
|
};
|
|
73
108
|
|
|
74
109
|
const onWheel = (event: WheelEvent) => {
|
|
110
|
+
if (event.ctrlKey) return; // pinch-zoom
|
|
75
111
|
const max = el[sizeProp] - el[clientProp];
|
|
76
112
|
if (max <= 0) return;
|
|
77
|
-
if (!animating) target = el[axisProp];
|
|
78
113
|
|
|
79
|
-
const
|
|
114
|
+
const now = performance.now();
|
|
115
|
+
const source = classify(event, now);
|
|
116
|
+
lastTs = now;
|
|
117
|
+
|
|
118
|
+
if (horizontal) {
|
|
119
|
+
// Native handles a real horizontal swipe; we only translate the
|
|
120
|
+
// vertical wheel (mouse, or a trackpad's vertical swipe) into
|
|
121
|
+
// horizontal motion — that translation is the reason the mode exists.
|
|
122
|
+
if (event.deltaX !== 0 && Math.abs(event.deltaX) >= Math.abs(event.deltaY)) {
|
|
123
|
+
releaseToNative();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
} else if (source === "trackpad") {
|
|
127
|
+
// Vertical mode: let the OS scroll the trackpad natively.
|
|
128
|
+
releaseToNative();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let delta =
|
|
133
|
+
horizontal && event.deltaX !== 0 ? event.deltaX : event.deltaY;
|
|
134
|
+
// deltaMode: 0 = pixel, 1 = line, 2 = page.
|
|
135
|
+
if (event.deltaMode === 1) delta *= 16;
|
|
136
|
+
else if (event.deltaMode === 2) delta *= el[clientProp];
|
|
137
|
+
delta *= multiplier;
|
|
80
138
|
if (delta === 0) return;
|
|
81
139
|
|
|
140
|
+
if (!animating) target = el[axisProp];
|
|
141
|
+
|
|
82
142
|
const atStart = target <= 0;
|
|
83
143
|
const atEnd = target >= max;
|
|
84
144
|
// Let the wheel bubble at the edges so the page keeps scrolling.
|
|
85
|
-
if ((delta < 0 && atStart) || (delta > 0 && atEnd))
|
|
145
|
+
if ((delta < 0 && atStart) || (delta > 0 && atEnd)) {
|
|
146
|
+
releaseToNative();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
86
149
|
|
|
87
150
|
event.preventDefault();
|
|
88
151
|
target = Math.max(0, Math.min(target + delta, max));
|