@udixio/ui-react 1.6.4 → 2.0.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/CHANGELOG.md +37 -3
- package/dist/index.cjs +3 -3
- package/dist/index.js +1710 -1449
- package/dist/lib/config/config.interface.d.ts +4 -0
- package/dist/lib/config/config.interface.d.ts.map +1 -0
- package/dist/lib/config/define-config.d.ts +4 -0
- package/dist/lib/config/define-config.d.ts.map +1 -0
- package/dist/lib/config/index.d.ts +3 -0
- package/dist/lib/config/index.d.ts.map +1 -0
- package/dist/lib/effects/AnimateOnScroll.d.ts +15 -0
- package/dist/lib/effects/AnimateOnScroll.d.ts.map +1 -0
- package/dist/lib/effects/ThemeProvider.d.ts +3 -2
- package/dist/lib/effects/ThemeProvider.d.ts.map +1 -1
- package/dist/lib/effects/block-scroll.effect.d.ts +22 -0
- package/dist/lib/effects/block-scroll.effect.d.ts.map +1 -0
- package/dist/lib/effects/custom-scroll/custom-scroll.effect.d.ts.map +1 -1
- package/dist/lib/effects/index.d.ts +1 -0
- package/dist/lib/effects/index.d.ts.map +1 -1
- package/dist/lib/effects/scrollDriven.d.ts +5 -0
- package/dist/lib/effects/scrollDriven.d.ts.map +1 -0
- package/dist/lib/effects/smooth-scroll.effect.d.ts +5 -5
- package/dist/lib/effects/smooth-scroll.effect.d.ts.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/styles/fab.style.d.ts.map +1 -1
- package/dist/scrollDriven-AP2yWhzi.js +121 -0
- package/dist/scrollDriven-DWAu7CR0.cjs +1 -0
- package/package.json +5 -3
- package/src/lib/config/config.interface.ts +9 -0
- package/src/lib/config/define-config.ts +16 -0
- package/src/lib/config/index.ts +2 -0
- package/src/lib/effects/AnimateOnScroll.tsx +353 -0
- package/src/lib/effects/ThemeProvider.tsx +78 -52
- package/src/lib/effects/block-scroll.effect.tsx +174 -0
- package/src/lib/effects/custom-scroll/custom-scroll.effect.tsx +16 -5
- package/src/lib/effects/index.ts +1 -0
- package/src/lib/effects/scrollDriven.ts +239 -0
- package/src/lib/effects/smooth-scroll.effect.tsx +105 -72
- package/src/lib/index.ts +1 -0
- package/src/lib/styles/card.style.ts +1 -1
- package/src/lib/styles/fab.style.ts +9 -17
- package/src/lib/styles/slider.style.ts +2 -2
- package/src/lib/styles/tab.style.ts +1 -1
- package/src/lib/styles/tabs.style.ts +3 -3
|
@@ -90,21 +90,32 @@ export const CustomScroll = ({
|
|
|
90
90
|
handleScrollThrottledRef.current = throttle(
|
|
91
91
|
throttleDuration,
|
|
92
92
|
(latestValue, scrollOrientation: 'x' | 'y') => {
|
|
93
|
-
if (
|
|
93
|
+
if (
|
|
94
|
+
!containerSize.current ||
|
|
95
|
+
!contentScrollSize.current ||
|
|
96
|
+
!ref.current
|
|
97
|
+
)
|
|
98
|
+
return;
|
|
94
99
|
if (onScroll) {
|
|
95
100
|
if (orientation === 'horizontal' && scrollOrientation === 'x') {
|
|
96
101
|
onScroll({
|
|
97
102
|
scrollProgress: latestValue,
|
|
98
|
-
scroll:
|
|
99
|
-
|
|
103
|
+
scroll:
|
|
104
|
+
latestValue *
|
|
105
|
+
(contentScrollSize.current.width - ref.current.clientWidth),
|
|
106
|
+
scrollTotal:
|
|
107
|
+
contentScrollSize.current.width - ref.current.clientWidth,
|
|
100
108
|
scrollVisible: containerSize.current.width,
|
|
101
109
|
});
|
|
102
110
|
}
|
|
103
111
|
if (orientation === 'vertical' && scrollOrientation === 'y') {
|
|
104
112
|
onScroll({
|
|
105
113
|
scrollProgress: latestValue,
|
|
106
|
-
scroll:
|
|
107
|
-
|
|
114
|
+
scroll:
|
|
115
|
+
latestValue *
|
|
116
|
+
(contentScrollSize.current.height - ref.current.clientHeight),
|
|
117
|
+
scrollTotal:
|
|
118
|
+
contentScrollSize.current.height - ref.current.clientHeight,
|
|
108
119
|
scrollVisible: containerSize.current.height,
|
|
109
120
|
});
|
|
110
121
|
}
|
package/src/lib/effects/index.ts
CHANGED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { CSSProperties } from 'react';
|
|
2
|
+
|
|
3
|
+
export type InitAnimationOptions = {
|
|
4
|
+
once?: boolean;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
let initialized = false;
|
|
8
|
+
let teardown: (() => void) | null = null;
|
|
9
|
+
|
|
10
|
+
function supportsScrollTimeline(): boolean {
|
|
11
|
+
if (typeof window === 'undefined') return false;
|
|
12
|
+
try {
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
if (window.CSS && typeof window.CSS.supports === 'function') {
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
return (
|
|
17
|
+
CSS.supports('animation-timeline: view()') ||
|
|
18
|
+
CSS.supports('animation-timeline: scroll()') ||
|
|
19
|
+
CSS.supports('view-timeline-name: --a')
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
} catch {}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function prefersReducedMotion(): boolean {
|
|
27
|
+
if (typeof window === 'undefined' || !('matchMedia' in window)) return false;
|
|
28
|
+
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Helpers to read CSS custom properties used by the Tailwind plugin
|
|
32
|
+
function readVar(el: Element, name: string): string | null {
|
|
33
|
+
const v = getComputedStyle(el).getPropertyValue(name).trim();
|
|
34
|
+
return v || null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parsePercentFromRangeToken(token?: string | null): number | null {
|
|
38
|
+
if (!token) return null;
|
|
39
|
+
// Expect patterns like "entry 20%", "cover 80%", "center 60%" etc.
|
|
40
|
+
const parts = token.split(/\s+/);
|
|
41
|
+
const last = parts[parts.length - 1];
|
|
42
|
+
if (!last) return null;
|
|
43
|
+
if (last.endsWith('%')) {
|
|
44
|
+
const n = parseFloat(last);
|
|
45
|
+
if (!isNaN(n)) return Math.max(0, Math.min(100, n)) / 100;
|
|
46
|
+
}
|
|
47
|
+
// px not supported for now (would require element size); fallback null
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getRange(el: Element): { start: number; end: number } {
|
|
52
|
+
const startToken = readVar(el, '--udx-range-start');
|
|
53
|
+
const endToken = readVar(el, '--udx-range-end');
|
|
54
|
+
const start = parsePercentFromRangeToken(startToken) ?? 0.2; // default entry 20%
|
|
55
|
+
const end = parsePercentFromRangeToken(endToken) ?? 0.5; // default cover 50%
|
|
56
|
+
// Ensure sane ordering
|
|
57
|
+
const s = Math.max(0, Math.min(1, start));
|
|
58
|
+
const e = Math.max(s + 0.001, Math.min(1, end));
|
|
59
|
+
return { start: s, end: e };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function num(val: string | null | undefined, unit?: 'px' | 'deg'): number | null {
|
|
63
|
+
if (!val) return null;
|
|
64
|
+
const v = val.trim();
|
|
65
|
+
if (unit && v.endsWith(unit)) {
|
|
66
|
+
const n = parseFloat(v);
|
|
67
|
+
return isNaN(n) ? null : n;
|
|
68
|
+
}
|
|
69
|
+
// Try plain number
|
|
70
|
+
const n = parseFloat(v);
|
|
71
|
+
return isNaN(n) ? null : n;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function lerp(a: number, b: number, t: number) {
|
|
75
|
+
return a + (b - a) * t;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function applyProgress(el: HTMLElement, from: CSSProperties, to: CSSProperties, p: number) {
|
|
79
|
+
// Opacity
|
|
80
|
+
const o0 = from.opacity != null ? Number(from.opacity) : 1;
|
|
81
|
+
const o1 = to.opacity != null ? Number(to.opacity) : 1;
|
|
82
|
+
const op = lerp(o0, o1, p);
|
|
83
|
+
el.style.opacity = String(op);
|
|
84
|
+
|
|
85
|
+
// Transform: translateX/Y (px only), scale, rotate (deg)
|
|
86
|
+
const fx = num((from as any)['--tw-enter-translate-x'] as any) ??
|
|
87
|
+
num((from as any)['--tw-exit-translate-x'] as any) ?? 0;
|
|
88
|
+
const fy = num((from as any)['--tw-enter-translate-y'] as any) ??
|
|
89
|
+
num((from as any)['--tw-exit-translate-y'] as any) ?? 0;
|
|
90
|
+
const tx = num((to as any)['--tw-enter-translate-x'] as any) ??
|
|
91
|
+
num((to as any)['--tw-exit-translate-x'] as any) ?? 0;
|
|
92
|
+
const ty = num((to as any)['--tw-enter-translate-y'] as any) ??
|
|
93
|
+
num((to as any)['--tw-exit-translate-y'] as any) ?? 0;
|
|
94
|
+
|
|
95
|
+
const fs = num((from as any)['--tw-enter-scale'] as any) ??
|
|
96
|
+
num((from as any)['--tw-exit-scale'] as any) ?? 1;
|
|
97
|
+
const ts = num((to as any)['--tw-enter-scale'] as any) ??
|
|
98
|
+
num((to as any)['--tw-exit-scale'] as any) ?? 1;
|
|
99
|
+
|
|
100
|
+
const fr = num((from as any)['--tw-enter-rotate'] as any) ??
|
|
101
|
+
num((from as any)['--tw-exit-rotate'] as any) ?? 0;
|
|
102
|
+
const tr = num((to as any)['--tw-enter-rotate'] as any) ??
|
|
103
|
+
num((to as any)['--tw-exit-rotate'] as any) ?? 0;
|
|
104
|
+
|
|
105
|
+
const x = lerp(fx, tx, p);
|
|
106
|
+
const y = lerp(fy, ty, p);
|
|
107
|
+
const s = lerp(fs, ts, p);
|
|
108
|
+
const r = lerp(fr, tr, p);
|
|
109
|
+
|
|
110
|
+
const transforms: string[] = [];
|
|
111
|
+
if (x !== 0 || y !== 0) transforms.push(`translate3d(${x}px, ${y}px, 0)`);
|
|
112
|
+
if (s !== 1) transforms.push(`scale(${s})`);
|
|
113
|
+
if (r !== 0) transforms.push(`rotate(${r}deg)`);
|
|
114
|
+
el.style.transform = transforms.length ? transforms.join(' ') : 'none';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildFromTo(el: Element): { from: CSSProperties; to: CSSProperties } | null {
|
|
118
|
+
const cls = el.classList;
|
|
119
|
+
const isIn = cls.contains('animate-in');
|
|
120
|
+
const isOut = cls.contains('animate-out');
|
|
121
|
+
if (!isIn && !isOut) return null;
|
|
122
|
+
|
|
123
|
+
const cs = getComputedStyle(el as Element);
|
|
124
|
+
const enter = {
|
|
125
|
+
opacity: num(cs.getPropertyValue('--tw-enter-opacity')) ?? undefined,
|
|
126
|
+
'--tw-enter-translate-x': cs.getPropertyValue('--tw-enter-translate-x') || undefined,
|
|
127
|
+
'--tw-enter-translate-y': cs.getPropertyValue('--tw-enter-translate-y') || undefined,
|
|
128
|
+
'--tw-enter-scale': cs.getPropertyValue('--tw-enter-scale') || undefined,
|
|
129
|
+
'--tw-enter-rotate': cs.getPropertyValue('--tw-enter-rotate') || undefined,
|
|
130
|
+
} as CSSProperties & Record<string, any>;
|
|
131
|
+
const exit = {
|
|
132
|
+
opacity: num(cs.getPropertyValue('--tw-exit-opacity')) ?? undefined,
|
|
133
|
+
'--tw-exit-translate-x': cs.getPropertyValue('--tw-exit-translate-x') || undefined,
|
|
134
|
+
'--tw-exit-translate-y': cs.getPropertyValue('--tw-exit-translate-y') || undefined,
|
|
135
|
+
'--tw-exit-scale': cs.getPropertyValue('--tw-exit-scale') || undefined,
|
|
136
|
+
'--tw-exit-rotate': cs.getPropertyValue('--tw-exit-rotate') || undefined,
|
|
137
|
+
} as CSSProperties & Record<string, any>;
|
|
138
|
+
|
|
139
|
+
if (isIn) {
|
|
140
|
+
// from enter vars to neutral
|
|
141
|
+
return {
|
|
142
|
+
from: enter,
|
|
143
|
+
to: { opacity: 1, '--tw-enter-translate-x': '0', '--tw-enter-translate-y': '0', '--tw-enter-scale': '1', '--tw-enter-rotate': '0' } as any,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (isOut) {
|
|
147
|
+
// from neutral to exit vars
|
|
148
|
+
return {
|
|
149
|
+
from: { opacity: 1, '--tw-exit-translate-x': '0', '--tw-exit-translate-y': '0', '--tw-exit-scale': '1', '--tw-exit-rotate': '0' } as any,
|
|
150
|
+
to: exit,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function findTargets(): HTMLElement[] {
|
|
157
|
+
const selector = [
|
|
158
|
+
'.udx-view',
|
|
159
|
+
'.udx-view-x',
|
|
160
|
+
'.udx-view-y',
|
|
161
|
+
'.udx-view-inline',
|
|
162
|
+
'.udx-view-block',
|
|
163
|
+
'[data-udx-view]'
|
|
164
|
+
]
|
|
165
|
+
.map((s) => `${s}.animate-in, ${s}.animate-out`)
|
|
166
|
+
.join(', ');
|
|
167
|
+
return Array.from(document.querySelectorAll<HTMLElement>(selector));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function initScrollViewFallback(options: InitAnimationOptions = {}) {
|
|
171
|
+
if (initialized) return teardown || (() => {});
|
|
172
|
+
if (typeof window === 'undefined') return () => {};
|
|
173
|
+
if (supportsScrollTimeline() || prefersReducedMotion()) {
|
|
174
|
+
initialized = true; // No-op in supporting browsers or reduced motion
|
|
175
|
+
return () => {};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
initialized = true;
|
|
179
|
+
const once = options.once ?? true;
|
|
180
|
+
const seen = new WeakSet<Element>();
|
|
181
|
+
let rafId: number | null = null;
|
|
182
|
+
|
|
183
|
+
const measure = () => {
|
|
184
|
+
const targets = findTargets();
|
|
185
|
+
const vh = window.innerHeight || 0;
|
|
186
|
+
for (const el of targets) {
|
|
187
|
+
const rect = el.getBoundingClientRect();
|
|
188
|
+
const visible = Math.min(rect.bottom, vh) - Math.max(rect.top, 0);
|
|
189
|
+
const visibleClamped = Math.max(0, Math.min(visible, rect.height));
|
|
190
|
+
const ratio = rect.height > 0 ? visibleClamped / rect.height : 0;
|
|
191
|
+
|
|
192
|
+
const { start, end } = getRange(el);
|
|
193
|
+
let p = (ratio - start) / (end - start);
|
|
194
|
+
p = Math.max(0, Math.min(1, p));
|
|
195
|
+
|
|
196
|
+
if (once && seen.has(el) && p < 1) p = 1;
|
|
197
|
+
|
|
198
|
+
const fe = buildFromTo(el);
|
|
199
|
+
if (!fe) continue;
|
|
200
|
+
const { from, to } = fe;
|
|
201
|
+
applyProgress(el, from, to, p);
|
|
202
|
+
|
|
203
|
+
if (p >= 1 && once) seen.add(el);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const onScroll = () => {
|
|
208
|
+
if (rafId != null) return;
|
|
209
|
+
rafId = window.requestAnimationFrame(() => {
|
|
210
|
+
rafId = null;
|
|
211
|
+
measure();
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// Initial run and listeners
|
|
216
|
+
measure();
|
|
217
|
+
window.addEventListener('scroll', onScroll, { passive: true });
|
|
218
|
+
window.addEventListener('resize', onScroll);
|
|
219
|
+
|
|
220
|
+
const mo = new MutationObserver(() => {
|
|
221
|
+
onScroll();
|
|
222
|
+
});
|
|
223
|
+
mo.observe(document.documentElement, {
|
|
224
|
+
childList: true,
|
|
225
|
+
subtree: true,
|
|
226
|
+
attributes: true,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
teardown = () => {
|
|
230
|
+
window.removeEventListener('scroll', onScroll);
|
|
231
|
+
window.removeEventListener('resize', onScroll);
|
|
232
|
+
if (rafId != null) cancelAnimationFrame(rafId);
|
|
233
|
+
mo.disconnect();
|
|
234
|
+
initialized = false;
|
|
235
|
+
teardown = null;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
return teardown;
|
|
239
|
+
}
|
|
@@ -1,91 +1,124 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
useTransform,
|
|
7
|
-
} from 'motion/react';
|
|
8
|
-
import { CustomScroll, CustomScrollInterface } from './custom-scroll';
|
|
9
|
-
import { classNames, ReactProps } from '../utils';
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { CustomScrollInterface } from './custom-scroll';
|
|
3
|
+
import { ReactProps } from '../utils';
|
|
4
|
+
import { BlockScroll } from './block-scroll.effect';
|
|
5
|
+
import { animate, AnimationPlaybackControls } from 'motion';
|
|
10
6
|
|
|
11
7
|
export const SmoothScroll = ({
|
|
12
|
-
|
|
13
|
-
transition = '.5s',
|
|
8
|
+
transition,
|
|
14
9
|
orientation = 'vertical',
|
|
15
10
|
throttleDuration = 25,
|
|
16
|
-
...restProps
|
|
17
11
|
}: {
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
transition?: {
|
|
13
|
+
duration?: number;
|
|
14
|
+
};
|
|
20
15
|
} & ReactProps<CustomScrollInterface>) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
scrollTotal: number;
|
|
24
|
-
scrollVisible: number;
|
|
25
|
-
scroll: number;
|
|
26
|
-
} | null>(null);
|
|
16
|
+
// Target value (instant), driven by wheel/touch/keyboard or native scroll sync
|
|
17
|
+
const [scrollY, setScrollY] = useState(0);
|
|
27
18
|
|
|
28
|
-
const
|
|
19
|
+
const [el, setEl] = useState<HTMLHtmlElement>();
|
|
29
20
|
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
[0, 1 - (scroll?.scrollVisible ?? 0) / (scroll?.scrollTotal ?? 0)]
|
|
34
|
-
);
|
|
21
|
+
const isScrolling = useRef(false);
|
|
22
|
+
const scrollTimeoutRef = useRef<NodeJS.Timeout>();
|
|
23
|
+
const lastAppliedYRef = useRef(0);
|
|
35
24
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
setEl(document as unknown as HTMLHtmlElement);
|
|
27
|
+
const y = document.documentElement.scrollTop;
|
|
28
|
+
setScrollY(y);
|
|
29
|
+
lastAppliedYRef.current = y;
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
// Sync native scroll (e.g., scrollbar, programmatic) back to target after a small delay
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const onScroll = () => {
|
|
35
|
+
if (isScrolling.current) return;
|
|
36
|
+
setScrollY(document.documentElement.scrollTop);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
el?.addEventListener('scroll', onScroll as unknown as EventListener);
|
|
40
|
+
return () => {
|
|
41
|
+
if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
|
|
42
|
+
el?.removeEventListener('scroll', onScroll as unknown as EventListener);
|
|
43
|
+
};
|
|
44
|
+
}, [el]);
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
// Drive the spring when target changes
|
|
47
|
+
const currentY = useRef<number | null>();
|
|
48
|
+
const animationRef = useRef<AnimationPlaybackControls | null>(null);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const y = scrollY;
|
|
51
|
+
|
|
52
|
+
if (animationRef.current) {
|
|
53
|
+
animationRef.current.stop();
|
|
54
|
+
animationRef.current = null;
|
|
50
55
|
}
|
|
51
|
-
};
|
|
52
|
-
const lastChangeTimeRef = useRef<number | null>(null);
|
|
53
|
-
useMotionValueEvent(percentTransform, 'change', (value) => {
|
|
54
|
-
// Récupérer l'heure actuelle
|
|
55
|
-
const now = performance.now();
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
console.log(`Delta temps : ${deltaTime} ms`);
|
|
57
|
+
if (!isScrolling.current) {
|
|
58
|
+
currentY.current = y;
|
|
59
|
+
return;
|
|
61
60
|
}
|
|
61
|
+
animationRef.current = animate(currentY.current ?? y, y, {
|
|
62
|
+
duration: transition?.duration ?? 0.5,
|
|
63
|
+
ease: 'circOut',
|
|
64
|
+
|
|
65
|
+
onUpdate: (value) => {
|
|
66
|
+
if (scrollTimeoutRef.current) {
|
|
67
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
68
|
+
}
|
|
69
|
+
currentY.current = value;
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
|
|
71
|
+
const html = document.documentElement;
|
|
72
|
+
// Avoid micro-movements causing extra layout work
|
|
73
|
+
const rounded = Math.round(value * 1000) / 1000;
|
|
74
|
+
const last = lastAppliedYRef.current;
|
|
75
|
+
if (Math.abs(rounded - last) < 0.1) return;
|
|
76
|
+
lastAppliedYRef.current = rounded;
|
|
65
77
|
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
if (isScrolling.current) {
|
|
79
|
+
html.scrollTo({ top: rounded });
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
onComplete: () => {
|
|
83
|
+
scrollTimeoutRef.current = setTimeout(() => {
|
|
84
|
+
isScrolling.current = false;
|
|
85
|
+
}, 300);
|
|
86
|
+
animationRef.current = null;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
return () => {
|
|
90
|
+
// Safety: stop if effect re-runs quickly
|
|
91
|
+
if (animationRef.current) {
|
|
92
|
+
animationRef.current.stop();
|
|
93
|
+
animationRef.current = null;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}, [scrollY]);
|
|
97
|
+
|
|
98
|
+
if (!el) return null;
|
|
68
99
|
|
|
69
100
|
return (
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
<BlockScroll
|
|
102
|
+
touch={false}
|
|
103
|
+
el={el as unknown as HTMLElement}
|
|
104
|
+
onScroll={(scroll) => {
|
|
105
|
+
if (
|
|
106
|
+
'deltaY' in scroll &&
|
|
107
|
+
scroll.deltaY !== 0 &&
|
|
108
|
+
el &&
|
|
109
|
+
scrollY !== null
|
|
110
|
+
) {
|
|
111
|
+
let y = scrollY + scroll.deltaY;
|
|
112
|
+
const html = el.querySelector('html');
|
|
113
|
+
if (html) {
|
|
114
|
+
y = Math.min(y, html.scrollHeight - html.clientHeight);
|
|
115
|
+
}
|
|
116
|
+
y = Math.max(y, 0);
|
|
117
|
+
setScrollY(y);
|
|
118
|
+
|
|
119
|
+
isScrolling.current = true;
|
|
120
|
+
}
|
|
121
|
+
}}
|
|
122
|
+
></BlockScroll>
|
|
90
123
|
);
|
|
91
124
|
};
|
package/src/lib/index.ts
CHANGED
|
@@ -5,7 +5,7 @@ export const cardStyle = defaultClassNames<CardInterface>(
|
|
|
5
5
|
'card',
|
|
6
6
|
({ variant, isInteractive }) => ({
|
|
7
7
|
card: classNames(
|
|
8
|
-
'
|
|
8
|
+
'group/card rounded-xl overflow-hidden z-10',
|
|
9
9
|
variant === 'outlined' && 'bg-surface border border-outline-variant',
|
|
10
10
|
variant === 'elevated' && 'bg-surface-container-low shadow-1',
|
|
11
11
|
variant === 'filled' && 'bg-surface-container-highest',
|
|
@@ -20,6 +20,10 @@ export const fabStyle = defaultClassNames<FabInterface>(
|
|
|
20
20
|
variant === 'primary' && 'bg-primary-container',
|
|
21
21
|
variant === 'secondary' && 'bg-secondary-container',
|
|
22
22
|
variant === 'tertiary' && 'bg-tertiary-container',
|
|
23
|
+
variant === 'surface' && 'text-primary',
|
|
24
|
+
variant === 'primary' && 'text-on-primary-container',
|
|
25
|
+
variant === 'secondary' && 'text-on-secondary-container',
|
|
26
|
+
variant === 'tertiary' && 'text-on-tertiary-container',
|
|
23
27
|
),
|
|
24
28
|
stateLayer: classNames(
|
|
25
29
|
'absolute w-full h-full overflow-hidden left-1/2 top-1/2 transform -translate-y-1/2 -translate-x-1/2',
|
|
@@ -33,23 +37,11 @@ export const fabStyle = defaultClassNames<FabInterface>(
|
|
|
33
37
|
'group-hover:hover-state-on-tertiary-container group-focus-visible:focus-state-on-tertiary-container',
|
|
34
38
|
),
|
|
35
39
|
|
|
36
|
-
icon: classNames(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
},
|
|
41
|
-
variant === 'surface' && 'text-primary',
|
|
42
|
-
variant === 'primary' && 'text-on-primary-container',
|
|
43
|
-
variant === 'secondary' && 'text-on-secondary-container',
|
|
44
|
-
variant === 'tertiary' && 'text-on-tertiary-container',
|
|
45
|
-
),
|
|
40
|
+
icon: classNames({
|
|
41
|
+
'size-6': size == 'small' || size == 'medium' || isExtended,
|
|
42
|
+
'size-9': size == 'large' && !isExtended,
|
|
43
|
+
}),
|
|
46
44
|
|
|
47
|
-
label: classNames(
|
|
48
|
-
'text-title-medium text-nowrap',
|
|
49
|
-
variant === 'surface' && 'text-primary',
|
|
50
|
-
variant === 'primary' && 'text-on-primary-container',
|
|
51
|
-
variant === 'secondary' && 'text-on-secondary-container',
|
|
52
|
-
variant === 'tertiary' && 'text-on-tertiary-container',
|
|
53
|
-
),
|
|
45
|
+
label: classNames('text-title-medium text-nowrap'),
|
|
54
46
|
}),
|
|
55
47
|
);
|
|
@@ -5,7 +5,7 @@ export const sliderStyle = defaultClassNames<SliderInterface>(
|
|
|
5
5
|
'slider',
|
|
6
6
|
({ isChanging }) => ({
|
|
7
7
|
slider: classNames([
|
|
8
|
-
'relative w-full h-11 flex items-center rounded gap-x-1.5 cursor-pointer',
|
|
8
|
+
'relative w-full h-11 flex items-center rounded gap-x-1.5 cursor-pointer min-w-32',
|
|
9
9
|
]),
|
|
10
10
|
activeTrack: classNames([
|
|
11
11
|
'h-4 relative transition-all duration-100 bg-primary overflow-hidden rounded-l-full ',
|
|
@@ -23,5 +23,5 @@ export const sliderStyle = defaultClassNames<SliderInterface>(
|
|
|
23
23
|
dot: classNames([
|
|
24
24
|
'h-1 w-1 absolute transform -translate-y-1/2 -translate-x-1/2 top-1/2 rounded-full',
|
|
25
25
|
]),
|
|
26
|
-
})
|
|
26
|
+
}),
|
|
27
27
|
);
|
|
@@ -6,7 +6,7 @@ export const tabStyle = defaultClassNames<TabInterface>(
|
|
|
6
6
|
'tab',
|
|
7
7
|
({ isSelected, icon, label, variant }) => ({
|
|
8
8
|
tab: classNames(
|
|
9
|
-
'
|
|
9
|
+
'flex-1 group outline-none flex px-4 justify-center items-center cursor-pointer',
|
|
10
10
|
{ 'z-10': isSelected },
|
|
11
11
|
Boolean(icon && label) && variant === 'primary' && 'h-16',
|
|
12
12
|
!(Boolean(icon && label) && variant === 'primary') && 'h-12',
|
|
@@ -5,9 +5,9 @@ export const tabsStyle = defaultClassNames<TabsInterface>(
|
|
|
5
5
|
'tabs',
|
|
6
6
|
({ scrollable }) => ({
|
|
7
7
|
tabs: classNames(
|
|
8
|
-
'border-b border-surface-container-highest',
|
|
8
|
+
'border-b border-surface-container-highest bg-surface',
|
|
9
9
|
'flex relative ',
|
|
10
|
-
{ 'overflow-x-auto': scrollable }
|
|
10
|
+
{ 'overflow-x-auto': scrollable },
|
|
11
11
|
),
|
|
12
|
-
})
|
|
12
|
+
}),
|
|
13
13
|
);
|