@galangel/react-scroll-magic
v1.0.3
Published
Powerful scroll component that feels like magic!
Downloads
11
Readme
✨ Key Features
| Feature | Description | | ------------------------- | --------------------------------------------------------------------------- | | 📌 Sticky Headers | Headers stick to top as you scroll, with support for nested sticky behavior | | 🎯 Nested Structure | Create deeply nested hierarchies with items inside items | | 📦 Collapse/Expand | Each section with nested content can be collapsed or expanded | | ♾️ Infinite Scrolling | Built-in support for loading more items when reaching the bottom | | 🎨 Fully Customizable | Complete control over rendering via render props | | 🔤 TypeScript Ready | Fully typed with comprehensive interfaces |
🚀 Quick Start
Installation
npm install @galangel/react-scroll-magicor
yarn add @galangel/react-scroll-magicBasic Usage
import { Scroll } from '@galangel/react-scroll-magic';
const items = [
{
id: 'section-1',
render: ({ collapse }) => (
<div style={{ padding: '10px', backgroundColor: '#f0f0f0' }}>
Header 1
{collapse && (
<button onClick={collapse.isOpen ? collapse.close : collapse.open}>{collapse.isOpen ? '▼' : '▶'}</button>
)}
</div>
),
nestedItems: [
{ render: () => <div style={{ padding: '10px' }}>Item 1.1</div> },
{ render: () => <div style={{ padding: '10px' }}>Item 1.2</div> },
],
},
{
id: 'section-2',
render: () => <div style={{ padding: '10px' }}>Simple Item</div>,
},
];
function App() {
return (
<div style={{ height: '400px', width: '100%' }}>
<Scroll items={items} headerBehavior="push" scrollBehavior="smooth" />
</div>
);
}📚 API Reference
Scroll Component Props
| Prop | Type | Default | Description |
| ---------------- | --------------------------------- | ---------- | -------------------------------------------------------------------------------------- |
| items | Items | Required | Array 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 |
| loading | Loading | Optional | Configuration for infinite scrolling |
Item Structure
interface Item {
id?: string; // Optional unique identifier
render: (props: {
// Render function for the item
collapse?: {
isOpen: boolean; // Current collapse state
open: () => void; // Function to expand
close: () => void; // Function to collapse
};
}) => JSX.Element;
nestedItems?: Item[]; // Optional nested items (makes this a header)
}Loading Type Definition
interface Loading {
onBottomReached?: () => Promise<void>; // Callback when user scrolls to bottom
render?: (isLoading: boolean) => JSX.Element; // Custom loading indicator renderer
}♾️ Infinite Scrolling Example
import React, { useState } from 'react';
import { Scroll } from '@galangel/react-scroll-magic';
const InfiniteScrollExample = () => {
const [items, setItems] = useState([
{ render: () => <div>Initial Item 1</div> },
{ render: () => <div>Initial Item 2</div> },
]);
const loadMoreItems = async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
const newItems = Array.from({ length: 10 }, (_, i) => ({
render: () => <div>New Item {items.length + i + 1}</div>,
}));
setItems((prev) => [...prev, ...newItems]);
};
const loading = {
onBottomReached: loadMoreItems,
render: (isLoading) => (
<div style={{ textAlign: 'center', padding: '20px' }}>{isLoading ? 'Loading...' : 'Load more'}</div>
),
};
return (
<div style={{ height: '400px', width: '100%' }}>
<Scroll items={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 Name | Element | Description |
| ------------------------- | ------- | ----------------------------------------------- |
| .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: 12px 16px;
background-color: #fff;
border-bottom: 1px solid #eee;
}
/* Header items - base styles */
.scroll-header {
padding: 16px 20px;
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, #667eea 0%, #764ba2 100%);
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.
<div style={{ height: '400px' }}>
{' '}
{/* or height: '100vh' */}
<Scroll items={items} />
</div>💡 Tips & Best Practices
| Tip | Description |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| 🎯 Use Unique IDs | Assign unique id properties to items for better performance and scroll-to functionality |
| 🛑 Stop Propagation | When adding click handlers inside items (like collapse buttons), use e.stopPropagation() to prevent scroll-to behavior |
| 📏 Set Container Height | The 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.
