app-studio 0.7.1 → 0.7.2
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/stories/ScrollAnimation.stories.d.ts +40 -8
- package/docs/Animation.md +288 -0
- package/docs/Hooks.md +205 -10
- package/docs/IframeSupport.md +743 -0
- package/docs/Theming.md +90 -0
- package/package.json +1 -1
|
@@ -1,19 +1,51 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ComponentStory } from '@storybook/react';
|
|
3
3
|
import { View } from '../src/index';
|
|
4
|
-
declare const
|
|
5
|
-
declare const _default: import("@storybook/types").ComponentAnnotations<import("@storybook/react/dist/types-0fc72a6d").R, {
|
|
6
|
-
children?: React.ReactNode;
|
|
7
|
-
}>;
|
|
4
|
+
declare const _default: import("@storybook/types").ComponentAnnotations<import("@storybook/react/dist/types-0fc72a6d").R, import("../src").ElementProps & React.RefAttributes<HTMLElement> & import("../src").ViewProps>;
|
|
8
5
|
export default _default;
|
|
9
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Demonstrates the useScrollAnimation hook with various configurations.
|
|
8
|
+
* This hook uses IntersectionObserver to track element visibility and progress.
|
|
9
|
+
*/
|
|
10
|
+
export declare const UseScrollAnimationHook: ComponentStory<typeof View>;
|
|
11
|
+
/**
|
|
12
|
+
* Demonstrates the useScroll hook for tracking scroll position and progress.
|
|
13
|
+
*/
|
|
14
|
+
export declare const UseScrollHook: ComponentStory<typeof View>;
|
|
15
|
+
/**
|
|
16
|
+
* Demonstrates the useScrollDirection hook.
|
|
17
|
+
*/
|
|
18
|
+
export declare const UseScrollDirectionHook: ComponentStory<typeof View>;
|
|
19
|
+
/**
|
|
20
|
+
* Demonstrates the useSmoothScroll hook for programmatic smooth scrolling.
|
|
21
|
+
*/
|
|
22
|
+
export declare const UseSmoothScrollHook: ComponentStory<typeof View>;
|
|
10
23
|
/**
|
|
11
24
|
* CSS View Timeline Animations - Pure CSS, no JavaScript state!
|
|
12
|
-
* These animations now work by default with animate prop (animateOn="Both" is default).
|
|
13
25
|
*/
|
|
14
|
-
export declare const
|
|
26
|
+
export declare const CSSViewTimelineAnimations: ComponentStory<typeof View>;
|
|
15
27
|
/**
|
|
16
28
|
* Entry + Exit animations combined
|
|
17
29
|
*/
|
|
18
30
|
export declare const EntryExitAnimations: ComponentStory<typeof View>;
|
|
19
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Scroll-driven text fill animation
|
|
33
|
+
*/
|
|
34
|
+
export declare const ScrollDrivenTextFill: ComponentStory<typeof View>;
|
|
35
|
+
/**
|
|
36
|
+
* FillText Examples - Using App-Studio's Theming System
|
|
37
|
+
*
|
|
38
|
+
* This story demonstrates FillText scroll animations integrated with App-Studio's
|
|
39
|
+
* color system. Colors use CSS variables for theme-awareness:
|
|
40
|
+
*
|
|
41
|
+
* - Palette colors: var(--color-blue-500), var(--color-emerald-500)
|
|
42
|
+
* - Alpha transparency: color-mix(in srgb, var(--color-blue-500) 20%, transparent)
|
|
43
|
+
* - Background colors: color.slate.900, color.emerald.950, etc.
|
|
44
|
+
*
|
|
45
|
+
* See docs/Theming.md for the complete color reference.
|
|
46
|
+
*/
|
|
47
|
+
export declare const FillTextExamples: ComponentStory<typeof View>;
|
|
48
|
+
/**
|
|
49
|
+
* All scroll-driven animation presets
|
|
50
|
+
*/
|
|
51
|
+
export declare const ScrollDrivenAnimationPresets: ComponentStory<typeof View>;
|
package/docs/Animation.md
CHANGED
|
@@ -189,6 +189,294 @@ These animations are linked to the scroll position associated with a timeline (u
|
|
|
189
189
|
<View animate={Animation.fadeInScroll()} />
|
|
190
190
|
```
|
|
191
191
|
|
|
192
|
+
### Custom Scroll Timeline with `animate.timeline`
|
|
193
|
+
|
|
194
|
+
For fine-grained control over scroll-driven animations, use the `animate` prop with `timeline` and `keyframes`:
|
|
195
|
+
|
|
196
|
+
```jsx
|
|
197
|
+
import { View, Vertical, Text } from 'app-studio';
|
|
198
|
+
|
|
199
|
+
function ScrollProgressBar() {
|
|
200
|
+
return (
|
|
201
|
+
<Vertical gap={20} padding={20} height="100vh">
|
|
202
|
+
<Text fontSize={24} fontWeight="bold">
|
|
203
|
+
Scroll Timeline Animation
|
|
204
|
+
</Text>
|
|
205
|
+
<Text>Scroll down to see the progress bar animate.</Text>
|
|
206
|
+
|
|
207
|
+
{/* Scroll container */}
|
|
208
|
+
<View
|
|
209
|
+
height={300}
|
|
210
|
+
overflow="auto"
|
|
211
|
+
border="1px solid #ccc"
|
|
212
|
+
padding={20}
|
|
213
|
+
position="relative"
|
|
214
|
+
>
|
|
215
|
+
{/* Progress Bar - sticks to top of container */}
|
|
216
|
+
<View
|
|
217
|
+
position="sticky"
|
|
218
|
+
top={0}
|
|
219
|
+
width="100%"
|
|
220
|
+
height={8}
|
|
221
|
+
backgroundColor="#ddd"
|
|
222
|
+
zIndex={10}
|
|
223
|
+
>
|
|
224
|
+
{/* Animated progress fill */}
|
|
225
|
+
<View
|
|
226
|
+
height="100%"
|
|
227
|
+
backgroundColor="color.blue.500"
|
|
228
|
+
width="0%"
|
|
229
|
+
animate={{
|
|
230
|
+
timeline: 'scroll()',
|
|
231
|
+
keyframes: {
|
|
232
|
+
from: { width: '0%' },
|
|
233
|
+
to: { width: '100%' },
|
|
234
|
+
},
|
|
235
|
+
}}
|
|
236
|
+
/>
|
|
237
|
+
</View>
|
|
238
|
+
|
|
239
|
+
{/* Scrollable content */}
|
|
240
|
+
<Vertical gap={50} paddingVertical={50}>
|
|
241
|
+
{[1, 2, 3, 4, 5].map((i) => (
|
|
242
|
+
<View key={i} padding={20} backgroundColor="#f5f5f5" borderRadius={8}>
|
|
243
|
+
Item {i}
|
|
244
|
+
</View>
|
|
245
|
+
))}
|
|
246
|
+
</Vertical>
|
|
247
|
+
</View>
|
|
248
|
+
</Vertical>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Timeline Options:**
|
|
254
|
+
|
|
255
|
+
| Timeline | Description |
|
|
256
|
+
|----------|-------------|
|
|
257
|
+
| `scroll()` | Links animation to the nearest scrollable ancestor's scroll progress |
|
|
258
|
+
| `scroll(root)` | Links to the root (viewport) scroll progress |
|
|
259
|
+
| `view()` | Links to element's visibility in the viewport (entry/exit) |
|
|
260
|
+
|
|
261
|
+
**Keyframe Properties:**
|
|
262
|
+
|
|
263
|
+
You can animate any CSS property between `from` and `to` states, or use percentage keyframes for more control:
|
|
264
|
+
|
|
265
|
+
```jsx
|
|
266
|
+
animate={{
|
|
267
|
+
timeline: 'scroll()',
|
|
268
|
+
keyframes: {
|
|
269
|
+
'0%': { opacity: 0, transform: 'translateY(20px)' },
|
|
270
|
+
'50%': { opacity: 1, transform: 'translateY(0)' },
|
|
271
|
+
'100%': { opacity: 0.5, transform: 'translateY(-10px)' },
|
|
272
|
+
},
|
|
273
|
+
}}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
See the [ScrollTimeline Story](../stories/ScrollTimeline.stories.tsx) for a complete example.
|
|
277
|
+
|
|
278
|
+
### FillText Scroll Animation with Theming
|
|
279
|
+
|
|
280
|
+
The `fillTextScroll()` animation creates a text reveal effect that fills as the user scrolls. It integrates with App-Studio's theming system using CSS variables.
|
|
281
|
+
|
|
282
|
+
#### Basic Usage
|
|
283
|
+
|
|
284
|
+
```jsx
|
|
285
|
+
import { View, Animation } from 'app-studio';
|
|
286
|
+
|
|
287
|
+
<View
|
|
288
|
+
as="span"
|
|
289
|
+
fontSize={48}
|
|
290
|
+
fontWeight="bold"
|
|
291
|
+
color="color.gray.500"
|
|
292
|
+
backgroundClip="text"
|
|
293
|
+
animate={Animation.fillTextScroll({
|
|
294
|
+
duration: '1s',
|
|
295
|
+
timingFunction: 'linear',
|
|
296
|
+
timeline: '--section',
|
|
297
|
+
range: 'entry 100% cover 55%',
|
|
298
|
+
})}
|
|
299
|
+
>
|
|
300
|
+
Text that fills as you scroll
|
|
301
|
+
</View>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#### Theme-Aware FillText with CSS Variables
|
|
305
|
+
|
|
306
|
+
For full theme integration, use CSS variables from the theming system. This ensures colors adapt to light/dark mode:
|
|
307
|
+
|
|
308
|
+
```jsx
|
|
309
|
+
import { View, Text } from 'app-studio';
|
|
310
|
+
import { Animation } from 'app-studio';
|
|
311
|
+
|
|
312
|
+
function ThemedFillText({ children, color = 'blue' }) {
|
|
313
|
+
// Map palette names to CSS variables
|
|
314
|
+
const colors = {
|
|
315
|
+
blue: {
|
|
316
|
+
fill: 'var(--color-blue-500)',
|
|
317
|
+
accent: 'var(--color-blue-400)',
|
|
318
|
+
base: 'color-mix(in srgb, var(--color-blue-500) 15%, transparent)',
|
|
319
|
+
underline: 'color-mix(in srgb, var(--color-blue-500) 20%, transparent)',
|
|
320
|
+
},
|
|
321
|
+
emerald: {
|
|
322
|
+
fill: 'var(--color-emerald-500)',
|
|
323
|
+
accent: 'var(--color-emerald-400)',
|
|
324
|
+
base: 'color-mix(in srgb, var(--color-emerald-500) 12%, transparent)',
|
|
325
|
+
underline: 'color-mix(in srgb, var(--color-emerald-500) 20%, transparent)',
|
|
326
|
+
},
|
|
327
|
+
violet: {
|
|
328
|
+
fill: 'var(--color-violet-500)',
|
|
329
|
+
accent: 'var(--color-violet-400)',
|
|
330
|
+
base: 'color-mix(in srgb, var(--color-violet-500) 10%, transparent)',
|
|
331
|
+
underline: 'color-mix(in srgb, var(--color-violet-500) 20%, transparent)',
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const { fill, accent, base, underline } = colors[color] || colors.blue;
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
<View
|
|
339
|
+
as="span"
|
|
340
|
+
fontSize={48}
|
|
341
|
+
fontWeight="bold"
|
|
342
|
+
css={`
|
|
343
|
+
color: ${base};
|
|
344
|
+
--fill-color: ${fill};
|
|
345
|
+
--accent: ${accent};
|
|
346
|
+
--underline-color: ${underline};
|
|
347
|
+
--underline-block-width: 200vmax;
|
|
348
|
+
--underline-width: 100%;
|
|
349
|
+
`}
|
|
350
|
+
backgroundImage={`
|
|
351
|
+
linear-gradient(90deg, transparent calc(100% - 1ch), var(--accent) calc(100% - 1ch)),
|
|
352
|
+
linear-gradient(90deg, var(--fill-color), var(--fill-color)),
|
|
353
|
+
linear-gradient(90deg, var(--underline-color), var(--underline-color))`}
|
|
354
|
+
backgroundSize={`
|
|
355
|
+
var(--underline-block-width) var(--underline-width),
|
|
356
|
+
var(--underline-block-width) var(--underline-width),
|
|
357
|
+
100% var(--underline-width)`}
|
|
358
|
+
backgroundRepeat="no-repeat"
|
|
359
|
+
backgroundPositionX="0"
|
|
360
|
+
backgroundPositionY="100%"
|
|
361
|
+
backgroundClip="text"
|
|
362
|
+
animate={Animation.fillTextScroll({
|
|
363
|
+
duration: '1s',
|
|
364
|
+
timingFunction: 'linear',
|
|
365
|
+
})}
|
|
366
|
+
>
|
|
367
|
+
{children}
|
|
368
|
+
</View>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Usage with different color palettes
|
|
373
|
+
<ThemedFillText color="blue">Blue themed text</ThemedFillText>
|
|
374
|
+
<ThemedFillText color="emerald">Emerald themed text</ThemedFillText>
|
|
375
|
+
<ThemedFillText color="violet">Violet themed text</ThemedFillText>
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
#### Using Color Palettes
|
|
379
|
+
|
|
380
|
+
All App-Studio color palettes work with FillText. Reference them via CSS variables:
|
|
381
|
+
|
|
382
|
+
| Palette | Fill Variable | Accent Variable |
|
|
383
|
+
|---------|--------------|-----------------|
|
|
384
|
+
| Blue | `var(--color-blue-500)` | `var(--color-blue-400)` |
|
|
385
|
+
| Emerald | `var(--color-emerald-500)` | `var(--color-emerald-400)` |
|
|
386
|
+
| Violet | `var(--color-violet-500)` | `var(--color-violet-400)` |
|
|
387
|
+
| Amber | `var(--color-amber-500)` | `var(--color-amber-400)` |
|
|
388
|
+
| Rose | `var(--color-rose-500)` | `var(--color-rose-400)` |
|
|
389
|
+
| Cyan | `var(--color-cyan-500)` | `var(--color-cyan-400)` |
|
|
390
|
+
| Gray (light bg) | `var(--color-gray-800)` | `var(--color-gray-900)` |
|
|
391
|
+
|
|
392
|
+
#### Alpha Transparency with `color-mix()`
|
|
393
|
+
|
|
394
|
+
For semi-transparent colors that remain theme-aware, use `color-mix()`:
|
|
395
|
+
|
|
396
|
+
```jsx
|
|
397
|
+
// 15% opacity blue
|
|
398
|
+
baseColor="color-mix(in srgb, var(--color-blue-500) 15%, transparent)"
|
|
399
|
+
|
|
400
|
+
// 20% opacity emerald
|
|
401
|
+
underlineColor="color-mix(in srgb, var(--color-emerald-500) 20%, transparent)"
|
|
402
|
+
|
|
403
|
+
// 70% opacity white
|
|
404
|
+
fillColor="color-mix(in srgb, var(--color-white) 70%, transparent)"
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
#### Complete Section Example
|
|
408
|
+
|
|
409
|
+
Here's a complete FillText section with view-timeline:
|
|
410
|
+
|
|
411
|
+
```jsx
|
|
412
|
+
function FillTextSection() {
|
|
413
|
+
return (
|
|
414
|
+
<View
|
|
415
|
+
css={`
|
|
416
|
+
@supports (animation-timeline: scroll()) {
|
|
417
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
418
|
+
view-timeline-name: --hero-section;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
`}
|
|
422
|
+
backgroundColor="color.slate.900"
|
|
423
|
+
>
|
|
424
|
+
<View height="50vh" />
|
|
425
|
+
<View as="main" height="200vh">
|
|
426
|
+
<View
|
|
427
|
+
as="section"
|
|
428
|
+
position="sticky"
|
|
429
|
+
top="0"
|
|
430
|
+
height="100vh"
|
|
431
|
+
display="grid"
|
|
432
|
+
placeItems="center"
|
|
433
|
+
>
|
|
434
|
+
<View padding="5ch" textAlign="center" maxWidth={1200}>
|
|
435
|
+
<View
|
|
436
|
+
as="span"
|
|
437
|
+
fontSize={56}
|
|
438
|
+
fontWeight="bold"
|
|
439
|
+
css={`
|
|
440
|
+
color: color-mix(in srgb, var(--color-blue-500) 15%, transparent);
|
|
441
|
+
--fill-color: var(--color-blue-500);
|
|
442
|
+
--accent: var(--color-blue-400);
|
|
443
|
+
--underline-color: color-mix(in srgb, var(--color-blue-500) 20%, transparent);
|
|
444
|
+
--underline-block-width: 200vmax;
|
|
445
|
+
--underline-width: 100%;
|
|
446
|
+
`}
|
|
447
|
+
backgroundImage={`
|
|
448
|
+
linear-gradient(90deg, transparent calc(100% - 1ch), var(--accent) calc(100% - 1ch)),
|
|
449
|
+
linear-gradient(90deg, var(--fill-color), var(--fill-color)),
|
|
450
|
+
linear-gradient(90deg, var(--underline-color), var(--underline-color))`}
|
|
451
|
+
backgroundSize={`
|
|
452
|
+
var(--underline-block-width) var(--underline-width),
|
|
453
|
+
var(--underline-block-width) var(--underline-width),
|
|
454
|
+
100% var(--underline-width)`}
|
|
455
|
+
backgroundRepeat="no-repeat"
|
|
456
|
+
backgroundPositionX="0"
|
|
457
|
+
backgroundPositionY="100%"
|
|
458
|
+
backgroundClip="text"
|
|
459
|
+
animate={Animation.fillTextScroll({
|
|
460
|
+
duration: '1s',
|
|
461
|
+
timingFunction: 'linear',
|
|
462
|
+
timeline: '--hero-section',
|
|
463
|
+
range: 'entry 100% cover 55%, cover 50% exit 0%',
|
|
464
|
+
})}
|
|
465
|
+
>
|
|
466
|
+
Build beautiful scroll-driven animations with pure CSS.
|
|
467
|
+
</View>
|
|
468
|
+
</View>
|
|
469
|
+
</View>
|
|
470
|
+
</View>
|
|
471
|
+
</View>
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
See [docs/Theming.md](./Theming.md) for the complete color palette reference.
|
|
477
|
+
|
|
478
|
+
See the [FillText Examples Story](../stories/ScrollAnimation.stories.tsx) for interactive demos with all color themes.
|
|
479
|
+
|
|
192
480
|
## 7. Event-Based Animations
|
|
193
481
|
|
|
194
482
|
You can trigger animations on interactions like hover, click, or focus using the `on` prop or underscore props (`_hover`, `_active`).
|
package/docs/Hooks.md
CHANGED
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
App-Studio provides a comprehensive set of React hooks to help you build interactive and responsive applications. This guide covers all the available hooks and their usage.
|
|
4
4
|
|
|
5
|
+
## Iframe Support
|
|
6
|
+
|
|
7
|
+
Many hooks in App-Studio support working inside iframes for micro-frontend architectures, preview environments, and embedded widgets.
|
|
8
|
+
|
|
9
|
+
**Supported hooks:** `useScroll`, `useScrollAnimation`, `useScrollDirection`, `useSmoothScroll`, `useClickOutside`, plus `ResponsiveProvider` and `WindowSizeProvider`.
|
|
10
|
+
|
|
11
|
+
See the dedicated [Iframe Support Guide](./IframeSupport.md) for complete documentation and examples.
|
|
12
|
+
|
|
5
13
|
## Scroll Hooks
|
|
6
14
|
|
|
7
15
|
### useScroll
|
|
8
16
|
|
|
9
|
-
A hook that tracks scroll position and progress for a container or window.
|
|
17
|
+
A hook that tracks scroll position and progress for a container or window. Supports tracking scroll inside iframes.
|
|
10
18
|
|
|
11
19
|
```tsx
|
|
12
20
|
import { useScroll } from 'app-studio';
|
|
@@ -27,17 +35,57 @@ function MyComponent() {
|
|
|
27
35
|
}
|
|
28
36
|
```
|
|
29
37
|
|
|
38
|
+
#### Tracking Scroll Inside an Iframe
|
|
39
|
+
|
|
40
|
+
When you pass an `HTMLIFrameElement` reference to the `container` option, `useScroll` automatically detects the iframe context and tracks scroll within the iframe's window:
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { useScroll } from 'app-studio';
|
|
44
|
+
|
|
45
|
+
function IframeScrollTracker() {
|
|
46
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
47
|
+
|
|
48
|
+
// Pass the iframe ref - useScroll will track the iframe's scroll position
|
|
49
|
+
const scrollPosition = useScroll({
|
|
50
|
+
container: iframeRef,
|
|
51
|
+
throttleMs: 50
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div>
|
|
56
|
+
<p>Iframe Scroll Y: {Math.round(scrollPosition.y)}px</p>
|
|
57
|
+
<p>Iframe Scroll Progress: {Math.round(scrollPosition.yProgress * 100)}%</p>
|
|
58
|
+
<iframe
|
|
59
|
+
ref={iframeRef}
|
|
60
|
+
src="/your-iframe-content"
|
|
61
|
+
style={{ width: '100%', height: '400px' }}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
30
68
|
**Options:**
|
|
31
69
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
70
|
+
| Option | Type | Default | Description |
|
|
71
|
+
|--------|------|---------|-------------|
|
|
72
|
+
| `container` | `RefObject<HTMLElement>` | window | Reference to the scrollable container. If an `HTMLIFrameElement` is passed, tracks the iframe's content scroll. |
|
|
73
|
+
| `offset` | `[number, number]` | `[0, 0]` | X and Y offset values to add to scroll position |
|
|
74
|
+
| `throttleMs` | `number` | `100` | Throttle interval in milliseconds for performance |
|
|
75
|
+
| `disabled` | `boolean` | `false` | Whether to disable scroll tracking |
|
|
76
|
+
|
|
77
|
+
**Returns:** `ScrollPosition`
|
|
78
|
+
|
|
79
|
+
| Property | Type | Description |
|
|
80
|
+
|----------|------|-------------|
|
|
81
|
+
| `x` | `number` | Horizontal scroll position in pixels |
|
|
82
|
+
| `y` | `number` | Vertical scroll position in pixels |
|
|
83
|
+
| `xProgress` | `number` | Horizontal scroll progress (0 to 1) |
|
|
84
|
+
| `yProgress` | `number` | Vertical scroll progress (0 to 1) |
|
|
37
85
|
|
|
38
86
|
### useScrollDirection
|
|
39
87
|
|
|
40
|
-
A hook that detects scroll direction.
|
|
88
|
+
A hook that detects scroll direction. Supports tracking direction inside iframes via the `targetWindow` parameter.
|
|
41
89
|
|
|
42
90
|
```tsx
|
|
43
91
|
import { useScrollDirection } from 'app-studio';
|
|
@@ -53,13 +101,35 @@ function ScrollDirectionComponent() {
|
|
|
53
101
|
}
|
|
54
102
|
```
|
|
55
103
|
|
|
104
|
+
#### With Iframe Support
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { useScrollDirection } from 'app-studio';
|
|
108
|
+
|
|
109
|
+
function IframeScrollDirection({ iframeWindow }: { iframeWindow: Window }) {
|
|
110
|
+
// Track scroll direction inside an iframe
|
|
111
|
+
const scrollDirection = useScrollDirection(5, iframeWindow);
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div>
|
|
115
|
+
Iframe scroll direction: {scrollDirection}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
56
121
|
**Parameters:**
|
|
57
122
|
|
|
58
|
-
|
|
123
|
+
| Parameter | Type | Default | Description |
|
|
124
|
+
|-----------|------|---------|-------------|
|
|
125
|
+
| `threshold` | `number` | `5` | Minimum scroll distance in pixels before direction change is detected |
|
|
126
|
+
| `targetWindow` | `Window` | `window` | Target window to track (use `iframe.contentWindow` for iframe support) |
|
|
127
|
+
|
|
128
|
+
**Returns:** `'up' | 'down'`
|
|
59
129
|
|
|
60
130
|
### useSmoothScroll
|
|
61
131
|
|
|
62
|
-
A hook that provides smooth scrolling functionality to elements.
|
|
132
|
+
A hook that provides smooth scrolling functionality to elements. Supports scrolling inside iframes via the `targetWindow` parameter.
|
|
63
133
|
|
|
64
134
|
```tsx
|
|
65
135
|
import { useSmoothScroll } from 'app-studio';
|
|
@@ -79,9 +149,42 @@ function SmoothScrollComponent() {
|
|
|
79
149
|
}
|
|
80
150
|
```
|
|
81
151
|
|
|
152
|
+
#### With Iframe Support
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
import { useSmoothScroll } from 'app-studio';
|
|
156
|
+
|
|
157
|
+
function IframeSmoothScroll({ iframeWindow }: { iframeWindow: Window }) {
|
|
158
|
+
// Smooth scroll inside an iframe
|
|
159
|
+
const scrollTo = useSmoothScroll(iframeWindow);
|
|
160
|
+
const targetRef = useRef<HTMLDivElement>(null);
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<>
|
|
164
|
+
<button onClick={() => scrollTo(targetRef.current, 80)}>
|
|
165
|
+
Scroll to Section in Iframe
|
|
166
|
+
</button>
|
|
167
|
+
<div ref={targetRef}>Target Section</div>
|
|
168
|
+
</>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Parameters:**
|
|
174
|
+
|
|
175
|
+
| Parameter | Type | Default | Description |
|
|
176
|
+
|-----------|------|---------|-------------|
|
|
177
|
+
| `targetWindow` | `Window` | `window` | Target window to scroll (use `iframe.contentWindow` for iframe support) |
|
|
178
|
+
|
|
179
|
+
**Returns:** `(element: HTMLElement | null, offset?: number) => void`
|
|
180
|
+
|
|
181
|
+
The returned function accepts:
|
|
182
|
+
- `element`: The element to scroll to
|
|
183
|
+
- `offset`: Optional offset from the top in pixels (default: 0)
|
|
184
|
+
|
|
82
185
|
### useScrollAnimation
|
|
83
186
|
|
|
84
|
-
A hook for creating scroll-linked animations using Intersection Observer.
|
|
187
|
+
A hook for creating scroll-linked animations using Intersection Observer. Supports automatic iframe detection and explicit `targetWindow` for cross-window observation.
|
|
85
188
|
|
|
86
189
|
```tsx
|
|
87
190
|
import { useScrollAnimation } from 'app-studio';
|
|
@@ -101,6 +204,98 @@ function ScrollAnimationComponent() {
|
|
|
101
204
|
}
|
|
102
205
|
```
|
|
103
206
|
|
|
207
|
+
#### With Multiple Thresholds
|
|
208
|
+
|
|
209
|
+
Use an array of thresholds to get granular progress updates:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
import { useScrollAnimation } from 'app-studio';
|
|
213
|
+
|
|
214
|
+
function AnimatedSection({ index }: { index: number }) {
|
|
215
|
+
const sectionRef = useRef<HTMLDivElement>(null);
|
|
216
|
+
|
|
217
|
+
const { isInView, progress } = useScrollAnimation(sectionRef, {
|
|
218
|
+
threshold: [0, 0.25, 0.5, 0.75, 1], // Fire at each 25% visibility
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div
|
|
223
|
+
ref={sectionRef}
|
|
224
|
+
style={{
|
|
225
|
+
opacity: 0.3 + progress * 0.7,
|
|
226
|
+
transform: `scale(${0.85 + progress * 0.15})`,
|
|
227
|
+
transition: 'opacity 0.4s, transform 0.4s',
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
<span>{isInView ? '✓ In View' : '○ Out of View'}</span>
|
|
231
|
+
<span>Progress: {(progress * 100).toFixed(0)}%</span>
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### With Iframe Support
|
|
238
|
+
|
|
239
|
+
The hook automatically detects when elements are inside an iframe and uses the correct `IntersectionObserver`. You can also explicitly pass `targetWindow`:
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
import { useScrollAnimation } from 'app-studio';
|
|
243
|
+
|
|
244
|
+
function IframeAnimatedSection({ targetWindow }: { targetWindow?: Window }) {
|
|
245
|
+
const sectionRef = useRef<HTMLDivElement>(null);
|
|
246
|
+
|
|
247
|
+
// Auto-detect iframe context or use explicit targetWindow
|
|
248
|
+
const { isInView, progress } = useScrollAnimation(sectionRef, {
|
|
249
|
+
threshold: [0, 0.25, 0.5, 0.75, 1],
|
|
250
|
+
targetWindow, // Optional: explicitly set the iframe's window
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<div
|
|
255
|
+
ref={sectionRef}
|
|
256
|
+
style={{
|
|
257
|
+
opacity: 0.3 + progress * 0.7,
|
|
258
|
+
background: isInView ? '#e8f5e9' : '#f5f5f5',
|
|
259
|
+
}}
|
|
260
|
+
>
|
|
261
|
+
Progress: {(progress * 100).toFixed(0)}%
|
|
262
|
+
</div>
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### With Callback
|
|
268
|
+
|
|
269
|
+
Use the `onIntersectionChange` callback for custom logic:
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
const { isInView, progress } = useScrollAnimation(ref, {
|
|
273
|
+
threshold: 0.5,
|
|
274
|
+
onIntersectionChange: (isIntersecting, ratio) => {
|
|
275
|
+
if (isIntersecting) {
|
|
276
|
+
analytics.track('section_viewed', { visibility: ratio });
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Options:**
|
|
283
|
+
|
|
284
|
+
| Option | Type | Default | Description |
|
|
285
|
+
|--------|------|---------|-------------|
|
|
286
|
+
| `threshold` | `number \| number[]` | `0` | Visibility threshold(s) to trigger updates (0-1) |
|
|
287
|
+
| `rootMargin` | `string` | `'0px'` | Margin around the root for intersection calculation |
|
|
288
|
+
| `root` | `Element \| null` | `null` | Custom root element for intersection |
|
|
289
|
+
| `targetWindow` | `Window \| null` | auto-detected | Target window for iframe support. Auto-detects from element's `ownerDocument` if not specified. |
|
|
290
|
+
| `onIntersectionChange` | `(isIntersecting: boolean, ratio: number) => void` | - | Callback fired on intersection changes |
|
|
291
|
+
|
|
292
|
+
**Returns:**
|
|
293
|
+
|
|
294
|
+
| Property | Type | Description |
|
|
295
|
+
|----------|------|-------------|
|
|
296
|
+
| `isInView` | `boolean` | Whether the element is currently in view |
|
|
297
|
+
| `progress` | `number` | Intersection ratio (0 to 1) based on threshold |
|
|
298
|
+
|
|
104
299
|
### useInfiniteScroll
|
|
105
300
|
|
|
106
301
|
A hook for implementing infinite scroll functionality.
|