storytellurx-v1/app/components/utils/positional-scroller.tsx
Elias Rhouzlane 91b337752a feat: enhance global background image and scrolling functionality
- Added a new filter to the GlobalBackgroundImageDisplay component for improved visual effects.
- Modified the PositionalScroller to update positions based on clicked elements, enhancing user interaction.
- Updated ScrollProgressTracker to allow scrolling effects to be applied to the body.
- Removed the deprecated use-field-sizing utility and replaced it with a more robust implementation.
- Improved persona styles for better layout and responsiveness in chat interfaces.
- Integrated a new ScrollArea component for better scroll handling in UI.
- Updated index and layout pages to reflect new components and improved structure.
- Added new dependencies for Radix UI components to enhance UI capabilities.
2025-06-30 19:42:05 +02:00

115 lines
3.7 KiB
TypeScript

"use client";
import { type HTMLAttributes, type ReactNode, useEffect, useRef } from "react";
interface PositionalScrollerProps extends HTMLAttributes<HTMLDivElement> {
children: ReactNode;
}
/**
* A container component that observes its direct children as they scroll.
* It identifies the single child closest to the container's center and assigns it
* `data-intersect-position="center"`. All other visible children are assigned
* "left" or "right". This enables creating carousel-like focus effects with CSS.
*/
export const PositionalScroller = ({
children,
...props
}: PositionalScrollerProps) => {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
// Track all child elements
const childElements = Array.from(container.children) as HTMLElement[];
const updatePositions = (clickedElement?: HTMLElement | null) => {
if (childElements.length === 0) return;
const containerRect = container.getBoundingClientRect();
const containerCenter = containerRect.left + containerRect.width / 2;
let centerElement: HTMLElement | null = null;
let minDistance = Infinity;
// Find visible elements and the one closest to center
const visibleElements: HTMLElement[] = [];
childElements.forEach((element) => {
const elementRect = element.getBoundingClientRect();
// Check if element is at least partially visible within container
const isVisible = elementRect.right > containerRect.left &&
elementRect.left < containerRect.right;
if (isVisible) {
visibleElements.push(element);
const elementCenter = elementRect.left + elementRect.width / 2;
const distance = Math.abs(elementCenter - containerCenter);
if (distance < minDistance) {
minDistance = distance;
centerElement = element;
}
} else {
// Remove attribute from non-visible elements
element.removeAttribute("data-intersect-position");
}
});
if (clickedElement) {
centerElement = clickedElement;
}
// Update positions for all visible elements
visibleElements.forEach((element) => {
if (element === centerElement) {
element.setAttribute("data-intersect-position", "center");
} else {
const elementRect = element.getBoundingClientRect();
const elementCenter = elementRect.left + elementRect.width / 2;
const position = elementCenter < containerCenter ? "left" : "right";
element.setAttribute("data-intersect-position", position);
}
});
};
// Use scroll event for smooth updates
const handleScroll = () => {
requestAnimationFrame(() => updatePositions());
};
// Also use ResizeObserver to handle container size changes
const resizeObserver = new ResizeObserver(() => {
requestAnimationFrame(() => updatePositions());
});
// Initial update
updatePositions();
// Add event listeners
const updatePositionsOnClick = (e: Event) =>
updatePositions((e.target as HTMLElement).closest("button"));
container.addEventListener("scroll", handleScroll, { passive: true });
container.addEventListener("click", updatePositionsOnClick, {
passive: true,
});
resizeObserver.observe(container);
// Cleanup
return () => {
container.removeEventListener("scroll", handleScroll);
container.removeEventListener("click", updatePositionsOnClick);
resizeObserver.disconnect();
};
}, [children]);
return (
<div ref={containerRef} {...props}>
{children}
</div>
);
};