Skip to content

🪝 Crúcaí: Irish for hooks 🪝 Bare at the moment but I'll be building out a library of random, unusual, but sometimes useful, React Hooks

Notifications You must be signed in to change notification settings

conorluddy/Crucai

Repository files navigation

Crúcai 🪝

Crúcai - Irish for "Hooks" - A React Hooks library

A collection of high-performance, zero-dependency (except for React), React hooks for building responsive and interactive UIs.

This is a Turborepo managed monorepo, with hooks as independent packages published to NPM.


GitHub Actions Workflow Status codecov Dynamic JSON Badge Last Commit Version

License: MIT NPM Version TypeScript React

📚 Table of Contents

🚀 Installation

Hooks can be installed individually, or as a full collection.

Install this whole repo

npm install crucai

Install only specific hook(s)

npm install @crucai/use-scroll-tracker
npm install @crucai/use-fuzzy-filter
...

🔌 Hooks Overview

useScrollTracker

A high-performance hook for tracking element visibility and position as users scroll.

import { useScrollTracker } from "crucai";

function FadeInElement() {
  const { ref, metrics } = useScrollTracker();

  return (
    <div
      ref={ref}
      style={{
        opacity: metrics.visibility.percentage / 100,
        transform: `translateY(${(1 - metrics.visibility.percentage / 100) * 20}px)`,
      }}
    >
      This element fades in as it enters the viewport
    </div>
  );
}

Key Features:

  • Visibility tracking: Percentage visible, fully/partially visible states
  • Position tracking: Relative to viewport top, center, bottom
  • Threshold detection: Track when element crosses specific visibility points
  • Scroll direction: Detect up/down scrolling
  • Scroll physics: Velocity, acceleration, inertia measurements
  • Entry/exit tracking: Direction, timing, duration
  • High performance: Uses IntersectionObserver, throttling, and passive events

Component API:

The hook also provides a component API using render props:

import { ScrollTracker } from "crucai";

function AnimatedElement() {
  return (
    <ScrollTracker>
      {(metrics, ref) => (
        <div
          ref={ref}
          style={{
            opacity: metrics.visibility.percentage / 100,
          }}
        >
          Animated content
        </div>
      )}
    </ScrollTracker>
  );
}

Options:

const { ref, metrics } = useScrollTracker({
  // Visibility thresholds to track (0-100)
  thresholds: [0, 25, 50, 75, 100],

  // Offset from top/bottom of viewport (e.g., for fixed headers/footers)
  offsetTop: 0,
  offsetBottom: 0,

  // Custom scroll container instead of window
  root: containerRef,

  // Other options for fine-tuning
  rootMargin: "0px 0px 0px 0px",
  disabled: false,
  throttleDelay: 0,

  // Physics-based animation control
  dynamics: {
    inertiaDecayTime: 300,
    maxVelocity: 1000,
    easing: "easeInOut",
    customEasingPoints: [0.33, 1, 0.68, 1],
  },
});

💡 Performance Tip: The hook is optimized to prevent re-renders when metrics haven't changed significantly, making it suitable for scroll-based animations without performance degradation.

useFuzzyFilter

A powerful hook for fuzzy text filtering with advanced matching capabilities.

import { useFuzzyFilter } from "crucai";

function SearchableList({ items }) {
  const { filteredItems, setQuery } = useFuzzyFilter({
    items,
    threshold: 2, // Max Levenshtein distance
  });

  return (
    <div>
      <input
        type="text"
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search items..."
      />
      <ul>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

Key Features:

  • Fuzzy searching: Finds close matches even with typos
  • Levenshtein distance: Controls how strict the matching is
  • Performance optimized: Uses trie data structure for efficient filtering
  • Customizable: Set match thresholds and search keys

Options:

const { filteredItems, setQuery } = useFuzzyFilter({
  // Items to filter (strings or objects)
  items: ["apple", "banana", "orange"],

  // If items are objects, specify which keys to search in
  keys: ["name", "description"],

  // Maximum Levenshtein distance for a match (default: 2)
  threshold: 2,

  // Initial search query (optional)
  initialQuery: "",

  // Whether to match entire query or individual words (default: false)
  matchByWord: true,

  // Sort results by relevance (default: true)
  sortResults: true,
});

🔍 Tip: Using a lower threshold (1-2) provides stricter matching, while higher values (3+) allow more fuzzy results.

📝 License

This project is licensed under the MIT License