- 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.
115 lines
3.7 KiB
TypeScript
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>
|
|
);
|
|
};
|