feat(fish-eye): implement fisheye effect for text with customizable scale and frequency feat(irregular-text): create irregular text container components for dynamic shape wrapping feat(positional-scroller): add positional scroller component to track child elements' positions during scroll feat(scroll-progress-tracker): implement scroll progress tracker to monitor scroll position and update CSS variables
69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
import cn from "@bureaudouble/outils/cn.ts";
|
|
import type { ElementType, ReactNode } from "react";
|
|
|
|
type IrregularShapeTextProps<T extends ElementType> = {
|
|
/** The content to be wrapped inside the shape. */
|
|
children: ReactNode;
|
|
/**
|
|
* The name of the CSS variable defining the left-side polygon.
|
|
* @default --shape-polygon-left
|
|
*/
|
|
leftShapeVar?: string;
|
|
/**
|
|
* The name of the CSS variable defining the right-side polygon.
|
|
* @default --shape-polygon-right
|
|
*/
|
|
rightShapeVar?: string;
|
|
/** Optional custom className for the wrapper element. */
|
|
className?: string;
|
|
/**
|
|
* The HTML tag to use for the wrapper element.
|
|
* @default div
|
|
*/
|
|
as?: T;
|
|
};
|
|
|
|
// This allows for passing component-specific props like `onClick` for a `button` etc.
|
|
type PolymorphicProps<T extends ElementType> =
|
|
& IrregularShapeTextProps<T>
|
|
& Omit<React.ComponentPropsWithoutRef<T>, keyof IrregularShapeTextProps<T>>;
|
|
|
|
export function IrregularShapeText<T extends ElementType = "div">({
|
|
children,
|
|
leftShapeVar = "--shape-polygon-left",
|
|
rightShapeVar = "--shape-polygon-right",
|
|
className,
|
|
as,
|
|
}: PolymorphicProps<T>) {
|
|
const Component = as || "div";
|
|
|
|
// These classes use Tailwind's arbitrary property syntax `[...]`.
|
|
// This is how you use CSS variables directly in your class names.
|
|
// We include the `-webkit-` prefix for maximum compatibility with older Safari.
|
|
const leftShapeClasses = cn(
|
|
"float-left",
|
|
"w-1/2", // The shaper div takes up the left half of the container
|
|
"h-full",
|
|
`[shape-outside:var(${leftShapeVar})]`,
|
|
`[-webkit-shape-outside:var(${leftShapeVar})]`,
|
|
);
|
|
|
|
const rightShapeClasses = cn(
|
|
"float-right",
|
|
"w-1/2", // The shaper div takes up the right half of the container
|
|
"h-full",
|
|
`[shape-outside:var(${rightShapeVar})]`,
|
|
`[-webkit-shape-outside:var(${rightShapeVar})]`,
|
|
);
|
|
|
|
return (
|
|
<Component className={cn("relative", className)}>
|
|
{/* These two divs are invisible but define the text flow boundaries */}
|
|
<div className={leftShapeClasses} />
|
|
<div className={rightShapeClasses} />
|
|
|
|
{/* The actual text content flows in the space between the floated divs */}
|
|
{children}
|
|
</Component>
|
|
);
|
|
}
|