Skip to content

Commit

Permalink
[REFACTOR]: File reorder (#30)
Browse files Browse the repository at this point in the history
* chore: add documentation, add example

* feat: wip

* fix: add missing gsap imports

* types: fix build and rename some types

* Update scrollytelling/src/types/index.ts

Co-authored-by: Joaquín R. Montes <joaquin@basement.studio>

* Revert "Update scrollytelling/src/types/index.ts"

This reverts commit 47ef057.

* semver: add changeset file

---------

Co-authored-by: Nazareno Oviedo <nazareno@basement.studio>
Co-authored-by: Nazareno Oviedo <nazarenooviedo@gmail.com>
  • Loading branch information
3 people authored Jun 21, 2023
1 parent 7570743 commit 4ed8eff
Show file tree
Hide file tree
Showing 14 changed files with 608 additions and 551 deletions.
5 changes: 5 additions & 0 deletions .changeset/serious-comics-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bsmnt/scrollytelling": minor
---

File reordering
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,4 @@ https://stackblitz.com/edit/react-ts-kittrj

## Next Steps

💡 [Core concepts](/docs/core-concepts.md)
💡 [Core concepts](/docs/core-concepts.md)
97 changes: 97 additions & 0 deletions scrollytelling/src/components/animation/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";



// ---- Types ----
import {
AnimationProps,
DataOrDataArray,
TweenWithChildrenDef,
TweenWithTargetDef,
} from "../../types";

// ---- Utils ----
import { buildDeclarativeTween, getTweenTarget } from "../../util";
import { useDispatcher, useScrollytelling } from "../../context";

/* -------------------------------------------------------------------------------------------------
* Animation
* -----------------------------------------------------------------------------------------------*/

/**
* Animation component enables defining animations using GSAP within the Scrollytelling context.
*
* @param {AnimationProps} props - Animation component props
* @returns {null | React.ReactElement} Animation component
* @link https://github.com/basementstudio/scrollytelling/blob/main/docs/api.md#animation
*/

export function Animation(props: {
tween: DataOrDataArray<TweenWithTargetDef>;
}): null;

export function Animation(props: {
children: React.ReactNode;
tween: DataOrDataArray<TweenWithChildrenDef>;
}): React.ReactElement;

export function Animation(props: AnimationProps): React.ReactElement | null {
const ref = React.useRef<HTMLElement>(null);
const id = React.useId();

const { timeline } = useScrollytelling();
const { getTimelineSpace } = useDispatcher();

React.useEffect(() => {
if (!timeline || !props.tween) return;

const addTweenToTimeline = (
tween: TweenWithChildrenDef | TweenWithTargetDef
) => {
const tweenTarget = getTweenTarget({
targetContainer: "target" in tween ? tween : {},
ref,
});
if (tweenTarget) {
const timelineSpace = getTimelineSpace({
start: tween.start,
end: tween.end,
});
const cleanup = buildDeclarativeTween({
id,
timeline,
op: tween,
target: tweenTarget,
duration: timelineSpace.duration,
position: timelineSpace.position,
});

return () => {
cleanup();
timelineSpace.cleanup();
};
} else return () => undefined;
};

if (Array.isArray(props.tween)) {
const cleanupTweens = props.tween.map((tween) => {
const cleanup = addTweenToTimeline(tween);
return cleanup;
});
return () => {
cleanupTweens.forEach((cleanup) => cleanup());
};
} else {
const cleanup = addTweenToTimeline(props.tween);
return () => {
cleanup();
};
}
}, [getTimelineSpace, id, props.tween, timeline]);

if (props.children) {
return <Slot ref={ref}>{props.children}</Slot>;
}
return null;
}
48 changes: 48 additions & 0 deletions scrollytelling/src/components/debugger/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as React from "react";
import { useScrollytelling } from "../../context";

// ---- Debugger

export const Debugger = () => {
const { timeline, rootRef } = useScrollytelling();
const [progress, setProgress] = React.useState<{
percentage: string;
px: string;
}>();

React.useEffect(() => {
if (!timeline || !rootRef.current) return;
const handleUpdate = () => {
const progress = timeline.progress();
if (!timeline.scrollTrigger) return;

const start = timeline.scrollTrigger.start;
const end = timeline.scrollTrigger.end;
const scroll = (end - start) * progress;
setProgress({
percentage: `${(progress * 100).toFixed(2)}%`,
px: `${scroll.toFixed(0)}px`,
});
};

timeline.eventCallback("onUpdate", handleUpdate);

return () => {
timeline.eventCallback("onUpdate", null);
};
}, [timeline, rootRef]);

return (
<div
style={{
width: "370px",
padding: "24px",
border: "1px solid black",
fontSize: "12px",
background: "#360202",
}}
>
<pre>{JSON.stringify({ progress }, null, 2)}</pre>
</div>
);
};
63 changes: 63 additions & 0 deletions scrollytelling/src/components/parallax/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import * as React from "react";

import { Animation } from "../animation";

/* -------------------------------------------------------------------------------------------------
* Parallax
* -----------------------------------------------------------------------------------------------*/

import { RequireAtLeastOne, StartEndOptions, TweenTarget, UnitValue } from "../../types";

interface ParallaxProps {
tween: StartEndOptions & {
target?: TweenTarget; // Optional: The target element or elements to apply the animation to.
} & RequireAtLeastOne<{
movementX?: UnitValue; // The amount of movement on the X-axis.
movementY?: UnitValue; // The amount of movement on the Y-axis.
}>;
children?: React.ReactNode; // Optional: Content to be rendered inside the Parallax component.
}

/**
* Applies a parallax effect to its children using GSAP animations.
*
* @returns {JSX.Element} `Animation` component with parallax effect applied. _(Expects `children`)_
* @link [[@bsmnt/scrollytelling] API Docs: Parallax](https://github.com/basementstudio/scrollytelling/blob/main/docs/api.md#parallax)
*/

export const Parallax: React.FC<ParallaxProps> = ({ children, tween }): JSX.Element => {
if (!tween.movementY && !tween.movementX) {
throw new Error(
"At least one of movementY and movementX is required in Parallax component."
);
}

return (
<Animation
tween={{
...tween,
fromTo: [
{
y: tween.movementY
? -1 * tween.movementY.value + tween.movementY.unit
: undefined,
x: tween.movementX
? -1 * tween.movementX.value + tween.movementX.unit
: undefined,
},
{
y: tween.movementY
? tween.movementY.value + tween.movementY.unit
: undefined,
x: tween.movementX
? tween.movementX.value + tween.movementX.unit
: undefined,
ease: "linear",
},
],
}}
>
{children}
</Animation>
);
};
60 changes: 60 additions & 0 deletions scrollytelling/src/components/pin/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as React from "react";

/* -------------------------------------------------------------------------------------------------
* Pin
* -----------------------------------------------------------------------------------------------*/

interface PinProps {
childHeight: string | number; // The height of the pinned element in the pin.
pinSpacerHeight: string | number; // The height of the spacer reserved for the pinned element in the pin.
childClassName?: string; // Optional: Custom CSS class name for the child element
children?: React.ReactNode; // Optional: Content to be rendered inside the pinned element
pinSpacerClassName?: string; // Optional: Custom CSS class name for the pin spacer element
top?: string | number; // Optional: Custom top position for the pinned element
}

/**
* Pin component enables pinning an element in its initial position while the remaining content scrolls.
* It ensures that the pinned element stays fixed at its starting position within the active duration of Scrollytelling.
*
* @param {PinProps} props - Pin component props
* @returns {JSX.Element} Pin component
* @link https://github.com/basementstudio/scrollytelling/blob/main/docs/api.md#pin
*/

export const Pin = React.forwardRef<HTMLDivElement, PinProps>(
(
{
childClassName,
childHeight,
children,
pinSpacerClassName,
pinSpacerHeight,
top = 0,
}: PinProps,
ref
) => {
if (!childHeight || !pinSpacerHeight) {
throw new Error(
"childHeight and pinSpacerHeight are required in Pin component."
);
}

return (
<div
className={pinSpacerClassName}
ref={ref}
style={{ height: pinSpacerHeight }}
>
<div
className={childClassName}
style={{ height: childHeight, position: "sticky", top }}
>
{children}
</div>
</div>
);
}
);

Pin.displayName = "Pin";
31 changes: 31 additions & 0 deletions scrollytelling/src/components/register-plugins/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { gsap } from "gsap";
// ---- Hooks ----
import { useIsoLayoutEffect } from "../../hooks/use-iso-layout-effect";

// ---- Types ----
import { Plugin } from "../../types";

/* -------------------------------------------------------------------------------------------------
* RegisterGsapPlugins
* -----------------------------------------------------------------------------------------------*/

interface RegisterGsapPluginsProps {
plugins: Plugin[];
}

/**
* RegisterGsapPlugins registers GSAP plugins.
*
* @param {RegisterGsapPluginsProps} props - RegisterGsapPlugins component props
* @returns {null}
* @link https://github.com/basementstudio/scrollytelling/blob/main/docs/api.md#register-plugin
*/

export const RegisterGsapPlugins = ({ plugins }: RegisterGsapPluginsProps) => {
// This needs to run before ScrollTrigger does any animations
useIsoLayoutEffect(() => {
gsap.registerPlugin(...plugins);
}, []);

return null;
};
Loading

1 comment on commit 4ed8eff

@vercel
Copy link

@vercel vercel bot commented on 4ed8eff Jun 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.