npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-magic

or

yarn add @galangel/react-scroll-magic

Basic 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.