Skip to main content

General

Scroll Spy code

Copy the implementation or inspect every file that belongs to this component unit.

resources/views/components/ui/scroll-spy.blade.php

@props([
    'items' => [],
    'offset' => 96,
])

@php
    /*
    | Anchor nav that highlights the section currently in view. Pass `items` as
    | [['id' => 'usage', 'label' => 'Usage'], ...] — matching element ids must
    | exist on the page. Highlighting is driven by an IntersectionObserver.
    |
    | Usage:
    |   <x-ui.scroll-spy :items="[
    |       ['id' => 'overview', 'label' => 'Overview'],
    |       ['id' => 'usage', 'label' => 'Usage'],
    |   ]" />
    */
    $normalized = collect($items)->map(fn ($item, $key) => is_array($item)
        ? ['id' => (string) ($item['id'] ?? $key), 'label' => (string) ($item['label'] ?? $key)]
        : ['id' => (string) $key, 'label' => (string) $item]
    )->values();
@endphp

<nav
    x-data="{
        active: null,
        ids: @js($normalized->pluck('id')->all()),
        init() {
            const observer = new IntersectionObserver((entries) => {
                entries.forEach((entry) => {
                    if (entry.isIntersecting) { this.active = entry.target.id; }
                });
            }, { rootMargin: '-{{ (int) $offset }}px 0px -60% 0px' });

            this.ids.forEach((id) => {
                const el = document.getElementById(id);
                if (el) { observer.observe(el); }
            });
        },
    }"
    aria-label="On this page"
    {{ $attributes->merge(['class' => 'space-y-0.5']) }}
>
    @foreach ($normalized as $item)
        <a
            href="#{{ $item['id'] }}"
            :class="active === @js($item['id'])
                ? 'border-brand-500 text-brand-600 dark:border-brand-400 dark:text-brand-400 font-medium'
                : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-700 dark:hover:text-gray-200'"
            class="block border-s-2 py-1.5 ps-3 text-sm transition"
        >{{ $item['label'] }}</a>
    @endforeach
</nav>