+
+ I will slide UP when scrolled into view!
+
+
+ I will slide DOWN.
+
+
+ I will slide LEFT.
+
+
+ I will slide RIGHT.
+
+
+ I will BLUR in elegantly.
+
+
+ );
+};
+console.log(Object.assign(ScrollAreaComponent, { item: ScrollAreaItem }));
+
+export { ScrollAreaComponent as ScrollArea };
+export { ScrollAreaItem as ScrollElement };
diff --git a/src/components/ScrollArea/ScrollArea.types.ts b/src/components/ScrollArea/ScrollArea.types.ts
new file mode 100644
index 0000000..484f407
--- /dev/null
+++ b/src/components/ScrollArea/ScrollArea.types.ts
@@ -0,0 +1,66 @@
+export interface ScrollAreaProps {
+ /** Unique identifier for the scrollable container. */
+ id?: string;
+
+ /** Ref forwarded to the underlying scroll element. */
+ ref?: React.RefObject;
+
+ /** Content rendered inside the scrollable container. */
+ children: React.ReactNode;
+
+ /** Additional class names applied to the container. */
+ className?: string;
+
+ /** Inline styles applied to the container. */
+ style?: React.CSSProperties;
+
+ /** Callback fired when the container is scrolled. */
+ onScroll?: (event: React.UIEvent) => void;
+
+ /**
+ * Controls the reveal animation applied to child items on scroll.
+ * - `"up"` / `"down"` / `"left"` / `"right"` — slides in from a direction
+ * - `"fade"` — fades in
+ * - `"scale"` — scales up from center
+ * - `"blur"` — unblurs into view
+ * @defaultValue `"fade"`
+ */
+ ScrollAnimation?:
+ | "up"
+ | "down"
+ | "left"
+ | "right"
+ | "fade"
+ | "scale"
+ | "blur";
+}
+
+export interface ScrollAreaItemProps {
+ /** Content rendered inside the scroll item. */
+ children: React.ReactNode;
+
+ /** Additional class names applied to the item. */
+ className?: string;
+
+ /** Inline styles applied to the item. */
+ style?: React.CSSProperties;
+
+ /**
+ * When `true`, the reveal animation only plays the first time the item
+ * enters the viewport. When `false`, it replays on every re-entry.
+ * @defaultValue `true`
+ */
+ triggerOnce?: boolean;
+
+ /**
+ * When `true`, the reveal animation only plays the first time the item
+ * enters the viewport. When `false`, it replays on every re-entry.
+ * @defaultValue `true`
+ */
+ threshold?: number;
+ /**
+ * Overrides the parent `ScrollArea` animation for this specific item.
+ * Inherits from `ScrollAreaProps.ScrollAnimation` when omitted.
+ */
+ revealType?: NonNullable;
+}
diff --git a/src/components/ScrollArea/index.ts b/src/components/ScrollArea/index.ts
new file mode 100644
index 0000000..755a2e0
--- /dev/null
+++ b/src/components/ScrollArea/index.ts
@@ -0,0 +1,2 @@
+export * from "./ScrollArea";
+export * from "./ScrollArea.types";
diff --git a/src/components/index.ts b/src/components/index.ts
index 66ea26d..0cbf216 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -7,6 +7,7 @@ export * from "./Badge";
export * from "./Avatar";
export * from "./Skeleton";
export * from "./Checkbox";
+export * from "./ScrollArea";
// ── Theme ──
export { ThemeProvider, useTheme, defaultTheme, darkTheme } from "../theme";
diff --git a/src/hooks/useReveal.ts b/src/hooks/useReveal.ts
new file mode 100644
index 0000000..a705d8e
--- /dev/null
+++ b/src/hooks/useReveal.ts
@@ -0,0 +1,46 @@
+import { useState, useEffect, useRef } from "react";
+
+interface UseRevealOptions {
+ threshold?: number | number[];
+ triggerOnce?: boolean;
+}
+
+export const useReveal = ({
+ threshold = 0,
+ triggerOnce = true,
+}: UseRevealOptions = {}) => {
+ const [isVisible, setIsVisible] = useState(false);
+ const elementRef = useRef(null);
+
+ useEffect(() => {
+ const observer = new IntersectionObserver(
+ (entries) => {
+ const entry = entries[0];
+
+ if (entry && entry.isIntersecting) {
+ setIsVisible(true);
+ if (triggerOnce && elementRef.current) {
+ observer.unobserve(elementRef.current);
+ }
+ } else if (!triggerOnce) {
+ setIsVisible(false);
+ }
+ },
+ { threshold },
+ );
+
+ const currentElement = elementRef.current;
+
+ if (currentElement) {
+ observer.observe(currentElement);
+ }
+
+ return () => {
+ if (currentElement) {
+ observer.unobserve(currentElement);
+ }
+ };
+ }, [threshold, triggerOnce]);
+
+ return { elementRef, isVisible };
+};
diff --git a/src/theme/DefaultTheme.ts b/src/theme/DefaultTheme.ts
index b7c9b72..4ca59d9 100644
--- a/src/theme/DefaultTheme.ts
+++ b/src/theme/DefaultTheme.ts
@@ -8,16 +8,16 @@ import type { ThemeConfig } from "./Types";
const palette = {
primary: {
- 50: "#eff6ff",
- 100: "#dbeafe",
- 200: "#bfdbfe",
- 300: "#93c5fd",
- 400: "#60a5fa",
- 500: "#3b82f6",
- 600: "#2563eb",
- 700: "#1d4ed8",
- 800: "#1e40af",
- 900: "#1e3a8a",
+ 50: "#fdf2f8",
+ 100: "#fce7f3",
+ 200: "#fbcfe8",
+ 300: "#f9a8d4",
+ 400: "#f472b6",
+ 500: "#ec4899",
+ 600: "#db2777",
+ 700: "#be185d",
+ 800: "#9d174d",
+ 900: "#831843",
},
success: {
50: "#f0fdf4",
diff --git a/src/theme/index.css b/src/theme/index.css
index cf50957..68bcb81 100644
--- a/src/theme/index.css
+++ b/src/theme/index.css
@@ -56,3 +56,217 @@
);
animation: skeleton-wave 1.6s linear infinite;
}
+
+/* ===== Modern Premium Scrollbar ===== */
+
+.scrollbar {
+ overflow: auto;
+ scrollbar-width: thin;
+ /* Firefox */
+ scrollbar-color: var(--color-border, rgba(150, 150, 150, 0.3)) transparent;
+}
+
+/* Scrollbar size */
+.scrollbar::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+}
+
+/* Track */
+.scrollbar::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+/* Thumb */
+.scrollbar::-webkit-scrollbar-thumb {
+ background-color: var(--color-border-subtle, rgba(150, 150, 150, 0.3));
+ border-radius: 9999px;
+ background-clip: padding-box;
+ transition: background-color 0.2s ease-in-out;
+}
+
+/* Hover */
+.scrollbar:hover::-webkit-scrollbar-thumb,
+.scrollbar::-webkit-scrollbar-thumb:hover {
+ background-color: var(--color-border, rgba(150, 150, 150, 0.5));
+}
+
+/* Active (dragging) */
+.scrollbar::-webkit-scrollbar-thumb:active {
+ background-color: var(--color-text-subtle, rgba(150, 150, 150, 0.7));
+}
+
+/* Remove arrows */
+.scrollbar::-webkit-scrollbar-button {
+ display: none !important;
+ width: 0 !important;
+ height: 0 !important;
+}
+
+/* Some Windows versions need this */
+.scrollbar::-webkit-scrollbar-button:start:decrement,
+.scrollbar::-webkit-scrollbar-button:end:increment {
+ display: none !important;
+}
+
+/* ===== Reveal Animations ===== */
+/* ===============================
+ Base Reveal Class
+================================ */
+
+.reveal {
+ opacity: 0;
+ will-change: transform, opacity, filter;
+ /* Add default transition in case no animation class is applied */
+ transition: all 0.8s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+/* When element becomes visible */
+.reveal.is-visible {
+ opacity: 1;
+}
+
+/* Optional stagger support */
+.reveal {
+ animation-delay: var(--delay, 0ms);
+}
+
+/* ===============================
+ 1. Reveal Up
+================================ */
+
+@keyframes reveal-up {
+ from {
+ opacity: 0;
+ transform: translateY(40px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.reveal-up.is-visible {
+ animation: reveal-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+/* ===============================
+ 2. Reveal Down
+================================ */
+
+@keyframes reveal-down {
+ from {
+ opacity: 0;
+ transform: translateY(-40px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.reveal-down.is-visible {
+ animation: reveal-down 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+/* ===============================
+ 3. Reveal Left
+================================ */
+
+@keyframes reveal-left {
+ from {
+ opacity: 0;
+ transform: translateX(40px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+.reveal-left.is-visible {
+ animation: reveal-left 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+/* ===============================
+ 4. Reveal Right
+================================ */
+
+@keyframes reveal-right {
+ from {
+ opacity: 0;
+ transform: translateX(-40px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+.reveal-right.is-visible {
+ animation: reveal-right 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+/* ===============================
+ 5. Fade In
+================================ */
+
+@keyframes fade-in {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+.fade-in.is-visible {
+ animation: fade-in 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+/* ===============================
+ 6. Scale Pop
+================================ */
+
+@keyframes reveal-scale {
+ from {
+ opacity: 0;
+ transform: scale(0.9);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+.reveal-scale.is-visible {
+ animation: reveal-scale 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+/* ===============================
+ 7. Premium Blur Reveal
+================================ */
+
+@keyframes reveal-blur {
+ from {
+ opacity: 0;
+ transform: translateY(30px) scale(0.98);
+ filter: blur(12px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ filter: blur(0);
+ }
+}
+
+.reveal-blur.is-visible {
+ animation: reveal-blur 1s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
From 3b01b355c0e20c82ed0f60a49249ee976dde8893 Mon Sep 17 00:00:00 2001
From: ABILASH <114130954+Abilash-19@users.noreply.github.com>
Date: Thu, 5 Mar 2026 23:52:33 +0530
Subject: [PATCH 06/17] docs: added documentation for scrollarea props
---
src/components/ScrollArea/ScrollArea.types.ts | 100 ++++++++++++++----
1 file changed, 77 insertions(+), 23 deletions(-)
diff --git a/src/components/ScrollArea/ScrollArea.types.ts b/src/components/ScrollArea/ScrollArea.types.ts
index 484f407..7ed02cd 100644
--- a/src/components/ScrollArea/ScrollArea.types.ts
+++ b/src/components/ScrollArea/ScrollArea.types.ts
@@ -1,29 +1,59 @@
+import type React from "react";
+
+/**
+ * Properties for the ScrollArea container component.
+ *
+ * The `ScrollArea` component acts as a highly customizable wrapper that coordinates
+ * reveal-on-scroll animations for its nested `ScrollAreaItem` descendants.
+ */
export interface ScrollAreaProps {
- /** Unique identifier for the scrollable container. */
+ /**
+ * Unique identifier applied to the scrollable container.
+ * Useful for accessibility (`aria-controls`) and testing scenarios.
+ */
id?: string;
- /** Ref forwarded to the underlying scroll element. */
- ref?: React.RefObject;
+ /**
+ * Ref forwarded internally to the underlying scroll container element (`
`).
+ * Can be leveraged to programmatically control scroll position.
+ */
+ ref?: React.RefObject;
- /** Content rendered inside the scrollable container. */
+ /**
+ * Content rendered inside the scrollable container.
+ * This typically includes standard HTML elements and `ScrollAreaItem` components
+ * which handle individual entry animations.
+ */
children: React.ReactNode;
- /** Additional class names applied to the container. */
+ /**
+ * Optional custom CSS class names to apply to the scroll container.
+ */
className?: string;
- /** Inline styles applied to the container. */
+ /**
+ * Optional inline styles to apply directly to the scroll container viewport.
+ */
style?: React.CSSProperties;
- /** Callback fired when the container is scrolled. */
+ /**
+ * Callback fired synchronously when the user scrolls the container's contents.
+ *
+ * @param event - The native UI event fired by the underlying `HTMLDivElement`.
+ */
onScroll?: (event: React.UIEvent) => void;
/**
- * Controls the reveal animation applied to child items on scroll.
- * - `"up"` / `"down"` / `"left"` / `"right"` — slides in from a direction
- * - `"fade"` — fades in
- * - `"scale"` — scales up from center
- * - `"blur"` — unblurs into view
- * @defaultValue `"fade"`
+ * Defines the default reveal animation applied to all child `ScrollAreaItem` descendant
+ * items when they enter the viewport. Items can override this using their own `revealType`.
+ *
+ * @remarks
+ * - `"up"`, `"down"`, `"left"`, `"right"`: Directional slide-in translations.
+ * - `"fade"`: Simple opacity transition from 0% to 100%.
+ * - `"scale"`: Zooms in fluidly from a lower scale bound.
+ * - `"blur"`: Transitions from a gaussian blur down into sharp focus.
+ *
+ * @defaultValue `"scale"`
*/
ScrollAnimation?:
| "up"
@@ -35,32 +65,56 @@ export interface ScrollAreaProps {
| "blur";
}
+/**
+ * Properties for an individual scroll-observable item.
+ *
+ * Specifically designed to be composed within a `ScrollArea` container. It leverages
+ * the intersection observer to trigger entrance and reveal animations.
+ */
export interface ScrollAreaItemProps {
- /** Content rendered inside the scroll item. */
+ /**
+ * The content to be wrapped and naturally revealed when intersected by the viewport.
+ */
children: React.ReactNode;
- /** Additional class names applied to the item. */
+ /**
+ * Optional custom CSS class names applied to the wrapper executing the intersection observer.
+ */
className?: string;
- /** Inline styles applied to the item. */
+ /**
+ * Optional inline styles applied directly to the observer wrapper element.
+ */
style?: React.CSSProperties;
/**
- * When `true`, the reveal animation only plays the first time the item
- * enters the viewport. When `false`, it replays on every re-entry.
+ * Determines whether the reveal animation occurs only a single time or repeatedly.
+ *
+ * - If `true`, the animation plays the first time the item is visible and remains unobserved mapping to a finalized state.
+ * - If `false`, the animation replays every single time the item crosses the threshold back into the viewport's context.
+ *
* @defaultValue `true`
*/
triggerOnce?: boolean;
/**
- * When `true`, the reveal animation only plays the first time the item
- * enters the viewport. When `false`, it replays on every re-entry.
- * @defaultValue `true`
+ * Set the threshold ratio indicating the percentage of the item's visibility required
+ * to trigger the reveal animation callbacks.
+ *
+ * - A value of `0.0` reveals instantly when any single pixel enters the viewport.
+ * - A value of `0.5` requires 50% visibility prior to executing the animation.
+ * - A value of `1.0` dictates the entire element must be thoroughly seen.
+ *
+ * @defaultValue 0.2
*/
threshold?: number;
+
/**
- * Overrides the parent `ScrollArea` animation for this specific item.
- * Inherits from `ScrollAreaProps.ScrollAnimation` when omitted.
+ * Disables the parent container's `ScrollAnimation` setting exclusively for this particular item.
+ * Highly useful for constructing complex cascades where certain hero items enter differently
+ * from the standard unified grid items.
+ *
+ * Inherits strictly from `ScrollAreaProps["ScrollAnimation"]` when left omitted.
*/
revealType?: NonNullable;
}
From f9d34f3db948998cdc5eb05d4433548eeabd38b6 Mon Sep 17 00:00:00 2001
From: ABILASH <114130954+Abilash-19@users.noreply.github.com>
Date: Thu, 5 Mar 2026 23:53:32 +0530
Subject: [PATCH 07/17] fix: added threshold to e user defined
---
src/components/ScrollArea/ScrollArea.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/ScrollArea/ScrollArea.tsx b/src/components/ScrollArea/ScrollArea.tsx
index 968621f..e233900 100644
--- a/src/components/ScrollArea/ScrollArea.tsx
+++ b/src/components/ScrollArea/ScrollArea.tsx
@@ -37,8 +37,9 @@ export const ScrollAreaItem = ({
style,
triggerOnce,
revealType = "scale",
+ threshold = 0.1,
}: ScrollAreaItemProps) => {
- const { elementRef, isVisible } = useReveal({ triggerOnce });
+ const { elementRef, isVisible } = useReveal({ triggerOnce, threshold });
const ANIMATION_CLASS_MAP: Record<
NonNullable,
@@ -63,7 +64,6 @@ export const ScrollAreaItem = ({