Skip to content

Repository files navigation

React Scroll Magic

🪄 React Scroll Magic

Create magical scroll experiences with nested sticky headers, collapsible sections, and smooth animations.

npm versionnpm downloadslicenseTypeScriptReact 18+

📖 Live Demo & Documentation


✨ Key Features

FeatureDescription
📌 Sticky HeadersHeaders stick to top as you scroll, with support for nested sticky behavior
🎯 Nested StructureCreate deeply nested hierarchies with items inside items
📦 Collapse/ExpandEach section with nested content can be collapsed or expanded
♾️ Infinite ScrollingBuilt-in support for loading more items when reaching the bottom
🎨 Fully CustomizableComplete control over rendering via render props
🔤 TypeScript ReadyFully typed with comprehensive interfaces

🚀 Quick Start

Installation

npm install @galangel/react-scroll-magic

or

yarn add @galangel/react-scroll-magic

Basic Usage

import{Scroll}from'@galangel/react-scroll-magic';constitems=[{id: 'section-1',render: ({ collapse })=>(<divstyle={{padding: '10px',backgroundColor: '#f0f0f0'}}>
Header 1
{collapse&&(<buttononClick={collapse.isOpen ? collapse.close : collapse.open}>{collapse.isOpen ? '▼' : '▶'}</button>)}</div>),nestedItems: [{render: ()=><divstyle={{padding: '10px'}}>Item 1.1</div>},{render: ()=><divstyle={{padding: '10px'}}>Item 1.2</div>},],},{id: 'section-2',render: ()=><divstyle={{padding: '10px'}}>Simple Item</div>,},];functionApp(){return(<divstyle={{height: '400px',width: '100%'}}><Scrollitems={items}headerBehavior="push"scrollBehavior="smooth"/></div>);}

📚 API Reference

Scroll Component Props

PropTypeDefaultDescription
itemsItemsRequiredArray of items to render. Each item has a render function and optional nestedItems
stickTo'top' | 'bottom' | 'all''all'Where headers should stick when scrolling
scrollBehavior'auto' | 'instant' | 'smooth''smooth'CSS scroll-behavior when clicking headers
headerBehavior'stick' | 'push' | 'none''none'How headers behave when scrolling
loadingLoadingOptionalConfiguration for infinite scrolling

Item Structure

interfaceItem{id?: string;// Optional unique identifierrender: (props: {// Render function for the itemcollapse?: {isOpen: boolean;// Current collapse stateopen: ()=>void;// Function to expandclose: ()=>void;// Function to collapse};})=>JSX.Element;nestedItems?: Item[];// Optional nested items (makes this a header)}

Loading Type Definition

interfaceLoading{onBottomReached?: ()=>Promise<void>;// Callback when user scrolls to bottomrender?: (isLoading: boolean)=>JSX.Element;// Custom loading indicator renderer}

♾️ Infinite Scrolling Example

importReact,{useState}from'react';import{Scroll}from'@galangel/react-scroll-magic';constInfiniteScrollExample=()=>{const[items,setItems]=useState([{render: ()=><div>Initial Item 1</div>},{render: ()=><div>Initial Item 2</div>},]);constloadMoreItems=async()=>{awaitnewPromise((resolve)=>setTimeout(resolve,1000));constnewItems=Array.from({length: 10},(_,i)=>({render: ()=><div>New Item {items.length+i+1}</div>,}));setItems((prev)=>[...prev, ...newItems]);};constloading={onBottomReached: loadMoreItems,render: (isLoading)=>(<divstyle={{textAlign: 'center',padding: '20px'}}>{isLoading ? 'Loading...' : 'Load more'}</div>),};return(<divstyle={{height: '400px',width: '100%'}}><Scrollitems={items}loading={loading}headerBehavior="none"/></div>);};

🎨 Styling & CSS Classes

The component uses semantic CSS classes that you can target for custom styling. Here's a complete reference:

CSS Class Reference

Class NameElementDescription
.scroll-list<ul>Main scroll container element
.scroll-item<li>Regular list item (items without nestedItems)
.scroll-header<li>Header item (items with nestedItems)
.scroll-header.stick<li>Header with headerBehavior="stick"
.scroll-header.push<li>Header with headerBehavior="push"
.scroll-header.none<li>Header with headerBehavior="none"
.scroll-loading<li>Loading indicator container
.scroll-loading.loading<li>Loading indicator when actively loading

Styling Examples

/* Main scroll container */
.scroll-list {
list-style: none;
margin:0;
padding:0;
height:100%;
overflow-y: auto;
}
/* All items (headers and regular items) */
.scroll-item,
.scroll-header {
width:100%;
box-sizing: border-box;
}
/* Regular items */
.scroll-item {
padding:12px16px;
background-color:#fff;
border-bottom:1px solid #eee;
}
/* Header items - base styles */
.scroll-header {
padding:16px20px;
background-color:#f5f5f5;
font-weight:600;
cursor: pointer;
border-bottom:1px solid #ddd;
}
/* Sticky header behavior */
.scroll-header.stick {
position: sticky;
/* top/bottom values are set dynamically by the component */
}
/* Push header behavior */
.scroll-header.push {
position: sticky;
/* top value is set dynamically by the component */
}
/* Header hover effect */
.scroll-header:hover {
background-color:#e8e8e8;
}
/* Loading indicator */
.scroll-loading {
display: none;
padding:20px;
text-align: center;
}
.scroll-loading.loading {
display: block;
}
/* Loading spinner animation */
.scroll-loading.loading::after {
content:'';
display: inline-block;
width:20px;
height:20px;
border:2px solid #ccc;
border-top-color:#667eea;
border-radius:50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform:rotate(360deg);
}
}

Dark Theme Example

/* Dark theme styling */
.scroll-list {
background-color:#1a202c;
}
.scroll-item {
background-color:#2d3748;
color:#e2e8f0;
border-bottom:1px solid #4a5568;
}
.scroll-header {
background:linear-gradient(135deg,#667eea0%,#764ba2100%);
color:#fff;
border-bottom:1px solid rgba(255,255,255,0.1);
}
.scroll-header:hover {
filter:brightness(1.1);
}
.scroll-loading.loading::after {
border-color:#4a5568;
border-top-color:#667eea;
}

Important Notes

⚠️Container Height Required: The scroll container must have a defined height for scrolling to work properly.

<divstyle={{height: '400px'}}>{' '}{/* or height: '100vh' */}<Scrollitems={items}/></div>

💡 Tips & Best Practices

TipDescription
🎯 Use Unique IDsAssign unique id properties to items for better performance and scroll-to functionality
🛑 Stop PropagationWhen adding click handlers inside items (like collapse buttons), use e.stopPropagation() to prevent scroll-to behavior
📏 Set Container HeightThe Scroll component needs a container with a defined height (height: 100vh or fixed pixels)
🎨 headerBehavior: "push"The "push" mode creates a natural feel where headers push each other out of view

🤖 Real World Example: AI Chat

Check out the AI Chat demo showcasing a complex real-world use case with:

  • 💬 Question → Response Flow: Messages with nested reasoning steps
  • 🧠 Collapsible Reasoning: Auto-collapse when complete
  • 📜 Deep Nesting: Four levels of nesting working seamlessly

📄 License

This project is licensed under the Apache License 2.0. See the LICENSE file for details.


🤝 Contributing

Contributions are welcome! Please read the CONTRIBUTING guidelines before submitting a pull request.


💬 Contact

For any questions or feedback, please open an issue on GitHub.


Made with 🪄 by @galangel

Buy Me A Coffee

Releases

Sponsor this project

Packages

Used by

Contributors

Languages