From 72743178ec9f4a5f809c88b91cd322361d7a2f2b Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Wed, 24 Sep 2025 22:30:36 +0200 Subject: [PATCH 01/19] wip --- resources/js/Pages/infinite-scroll.jsx | 528 +++++++++++++++++++++++++ 1 file changed, 528 insertions(+) create mode 100644 resources/js/Pages/infinite-scroll.jsx diff --git a/resources/js/Pages/infinite-scroll.jsx b/resources/js/Pages/infinite-scroll.jsx new file mode 100644 index 00000000..34252ef2 --- /dev/null +++ b/resources/js/Pages/infinite-scroll.jsx @@ -0,0 +1,528 @@ +import { A, Code, CodeBlock, H1, H2, H3, Notice, P } from '@/Components' +import dedent from 'dedent-js' + +export const meta = { + title: 'Infinite Scroll', + links: [ + { url: '#top', name: 'Introduction' }, + { url: '#backend-setup', name: 'Backend setup' }, + { url: '#basic-usage', name: 'Basic usage' }, + { url: '#manual-mode', name: 'Manual mode' }, + { url: '#reverse-mode', name: 'Reverse mode' }, + { url: '#trigger-control', name: 'Trigger control' }, + { url: '#loading-buffer', name: 'Loading buffer' }, + { url: '#preserve-url', name: 'Preserve URL' }, + { url: '#slots', name: 'Slots' }, + { url: '#custom-element', name: 'Custom element' }, + { url: '#custom-triggers', name: 'Custom triggers' }, + { url: '#scroll-containers', name: 'Scroll containers' }, + { url: '#programmatic-access', name: 'Programmatic access' }, + { url: '#scroll-function', name: 'Inertia::scroll() function' }, + ], +} + +export default function () { + return ( + <> +

Infinite scroll

+

+ When displaying large datasets, traditional pagination can feel clunky as users have to click through multiple + pages to view content. Inertia's infinite scroll feature provides a more fluid way to browse content by automatically + loading additional pages as users scroll through your application. +

+

+ The {''} component handles the intersection observation and loading logic for you, + while Inertia's scroll() function on the server side prepares your paginated data for infinite scrolling. +

+ +

Server side

+

+ To prepare your paginated data for infinite scrolling, wrap it with Inertia's scroll() function. + This function configures the proper merge behavior and provides pagination metadata to the frontend component. +

+ Inertia::scroll( + User::paginate(15) + ) + ]); + } + } + `} + /> +

+ The scroll() function works with any Laravel pagination method and automatically configures the + data for infinite scrolling. +

+ +

Client side

+

+ On the frontend, you'll wrap your content with the {''} component and specify which data property contains + your paginated results using the data prop. +

+ +
+ {{ user.name }} +
+ + `} + /> +

+ The component will automatically load the next page when users scroll near the end of the content. +

+ +

Manual mode

+

+ Sometimes you may want to give users control over when additional content loads instead of loading automatically. + You can enable manual mode using the manual prop and provide load buttons through the after slot. +

+ + + + + `} + /> +

+ You can also configure the component to automatically switch to manual mode after a certain number of loads + using the manualAfter prop. +

+ + + + `} + /> + +

Reverse mode

+

+ For chat applications, timelines, or interfaces where new content appears at the top, you can enable reverse + mode. This configures the component to load older content when scrolling upward. +

+ + + + `} + /> +

+ The reverse prop configures the component to load older content when scrolling up, and also + enables automatic scrolling to the bottom on initial load. You can override this behavior with the{' '} + autoScroll prop if needed. +

+ + + + + + `} + /> + +

Trigger control

+

+ By default, infinite scroll loads content in both directions when you scroll near the start or end. You can + control which directions trigger loading using the trigger prop. The both option is + particularly useful when users start on a middle page (like page 2) and need to scroll both up to previous pages and down to newer pages. +

+ + + + + + + + + `} + /> + +

Loading buffer

+

+ You can control how early content begins loading by setting a buffer distance. This loads content before + users reach the end, making scrolling feel smoother. +

+ + `} + /> +

+ A larger buffer loads content earlier but may result in more requests. You should experiment with different + values to find the right balance for your application. +

+ +

Preserve URL

+

+ By default, the infinite scroll component updates the browser URL as new pages load, allowing users to bookmark + or share links to specific pages. You can disable this behavior to maintain the original page URL. +

+ + `} + /> +

+ This is useful when infinite scroll is used for secondary content that shouldn't affect the main page URL, + such as comments on a blog post or related products on a product page. +

+ +

Slots

+

+ The component provides several slots to customize how loading works. +

+ +

Default slot

+

+ The main content area where you render your data items. This slot receives loading state information. +

+ + +
+ `} + /> + +

Loading slot

+

+ The loading slot is used as a fallback when loading content and no custom before or{' '} + after slots are provided. This creates a default loading indicator. +

+ + + +
+ `} + /> + +

Before and after slots

+

+ The before and after slots are rendered above and below the main content, typically + used for manual load controls. These slots receive several properties including loading states, fetch functions, + and mode indicators. +

+ + + + + + +
+ `} + /> +

+ The slot properties include: +

+
    +
  • loading - Whether this direction is currently loading
  • +
  • loadingBefore - Whether content is loading before current data
  • +
  • loadingAfter - Whether content is loading after current data
  • +
  • fetch - Function to manually trigger loading for this direction
  • +
  • hasMore - Whether more content is available in this direction
  • +
  • manualMode - Whether manual mode is currently active
  • +
  • autoMode - Whether automatic loading is currently active
  • +
+ +

Custom element

+

+ By default, the component renders as a {'

'} element. You may customize this to use any HTML + element using the as prop. +

+ +
  • + {{ product.name }} +
  • + + `} + /> + +

    Custom triggers

    +

    + You may specify custom elements to serve as trigger points for loading content using the{' '} + beforeElement and afterElement props. You can also use the{' '} + slotElement prop to specify a custom container element for the main content area. +

    + +
    Before trigger
    + +
    After trigger
    + + `} + /> +

    + You can also use functions to dynamically determine trigger elements. +

    + + import { ref } from 'vue' + const beforeTrigger = ref(null) + const afterTrigger = ref(null) + + + + `} + /> +

    + You can also specify which element should be treated as the main content container using the{' '} + slotElement prop. +

    + + + + + + + + + + + + + + +
    Name
    {{ user.name }}
    Footer
    +
    + + `} + /> +

    + When using custom trigger elements, the component will not render its default before/after trigger + elements, but it will still register intersection observers on your custom elements to detect when + they enter the viewport. This gives you complete control over the trigger positioning and + styling within your content structure. +

    + +

    Scroll containers

    +

    + The infinite scroll component detects scrollable parent containers automatically. This means you can use it + within custom scroll containers without additional configuration. +

    + + + + +
    + `} + /> +

    + The component uses the scrollable container for trigger detection and scroll calculations automatically. +

    + +

    Programmatic access

    +

    + When you need to trigger loading actions programmatically, you may use a template ref. This provides an + alternative to slot props when building more complex interfaces. +

    + + import { ref } from 'vue' + const infiniteScrollRef = ref(null) + + const loadMore = () => { + infiniteScrollRef.value?.loadAfter() + } + + + + `} + /> +

    + The component exposes the following methods: +

    +
      +
    • loadAfter() - Manually load the next page
    • +
    • loadBefore() - Manually load the previous page
    • +
    • hasMoreAfter() - Check if more content is available after current data
    • +
    • hasMoreBefore() - Check if more content is available before current data
    • +
    + +

    Inertia::scroll() function

    +

    + The Inertia::scroll() function provides server-side configuration for infinite scrolling. + In addition to automatically calling append('data') or prepend('data'), it + normalizes pagination metadata for the frontend, regardless of which Laravel pagination method you use. +

    +

    + The function works with all of Laravel's pagination approaches, creating a consistent interface whether you're + using standard pagination, cursor pagination, simple pagination, or custom pagination wrapped within API + resources. +

    + +

    + If you don't use Laravel's Paginator or use a different transformation layer, you may use the additional + arguments that scroll() accepts. +

    + +

    + The metadata parameter accepts an instance of ProvidesScrollMetadata or a callback that returns + such an instance. The callback receives the $data parameter. This is useful when integrating + with third-party pagination libraries like Fractal. +

    + +

    + You may then use this custom metadata provider in your scroll function. +

    + $this->transformData($data), + metadata: fn ($data) => new FractalScrollMetadata($data) + ); + `} + /> +

    + You may create a macro for frequently used patterns. +

    + new FractalScrollMetadata($data) + ); + }); + + // Then use it in your controllers + return Inertia::render('Users/Index', [ + 'users' => Inertia::fractalScroll($fractalCollection) + ]); + `} + /> + + The scroll() function automatically handles merge intent headers sent by the frontend component, + ensuring that data is appended or prepended based on the user's scrolling direction. + + + ) +} \ No newline at end of file From bfc6f1db678500085ff54f3784f8e1bc12c39510 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Wed, 24 Sep 2025 22:43:38 +0200 Subject: [PATCH 02/19] wip --- resources/js/Components/Nav.jsx | 5 ++ resources/js/Pages/infinite-scroll.jsx | 65 +++++++++++--------------- 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/resources/js/Components/Nav.jsx b/resources/js/Components/Nav.jsx index ff7f52e2..6504eb7d 100644 --- a/resources/js/Components/Nav.jsx +++ b/resources/js/Components/Nav.jsx @@ -174,6 +174,11 @@ const Nav = ({ className }) => { Load when visible +
  • + + Infinite scroll + +
  • Remembering state diff --git a/resources/js/Pages/infinite-scroll.jsx b/resources/js/Pages/infinite-scroll.jsx index 34252ef2..977df57c 100644 --- a/resources/js/Pages/infinite-scroll.jsx +++ b/resources/js/Pages/infinite-scroll.jsx @@ -5,8 +5,8 @@ export const meta = { title: 'Infinite Scroll', links: [ { url: '#top', name: 'Introduction' }, - { url: '#backend-setup', name: 'Backend setup' }, - { url: '#basic-usage', name: 'Basic usage' }, + { url: '#server-side', name: 'Server-side' }, + { url: '#client-side', name: 'Client-side' }, { url: '#manual-mode', name: 'Manual mode' }, { url: '#reverse-mode', name: 'Reverse mode' }, { url: '#trigger-control', name: 'Trigger control' }, @@ -26,16 +26,13 @@ export default function () { <>

    Infinite scroll

    - When displaying large datasets, traditional pagination can feel clunky as users have to click through multiple - pages to view content. Inertia's infinite scroll feature provides a more fluid way to browse content by automatically - loading additional pages as users scroll through your application. + Inertia's infinite scroll feature allows you to automatically load additional pages of content as users scroll through your application. This eliminates the need for traditional pagination controls and creates a more fluid, social media-like interface.

    - The {''} component handles the intersection observation and loading logic for you, - while Inertia's scroll() function on the server side prepares your paginated data for infinite scrolling. + The {''} component handles intersection observation and loading logic automatically. To configure your server-side data for infinite scrolling, you can use the Inertia::scroll() method. Together, they support bidirectional loading, manual controls, and advanced features like reverse mode for chat interfaces.

    -

    Server side

    +

    Server-side

    To prepare your paginated data for infinite scrolling, wrap it with Inertia's scroll() function. This function configures the proper merge behavior and provides pagination metadata to the frontend component. @@ -43,19 +40,11 @@ export default function () { Inertia::scroll( - User::paginate(15) - ) - ]); - } - } + Route::get('/users', function () { + return Inertia::render('Users/Index', [ + 'users' => Inertia::scroll(fn () => User::paginate()) + ]); + }); `} />

    @@ -63,7 +52,7 @@ export default function () { data for infinite scrolling.

    -

    Client side

    +

    Client-side

    On the frontend, you'll wrap your content with the {''} component and specify which data property contains your paginated results using the data prop. @@ -85,14 +74,14 @@ export default function () {

    Manual mode

    Sometimes you may want to give users control over when additional content loads instead of loading automatically. - You can enable manual mode using the manual prop and provide load buttons through the after slot. + You can enable manual mode using the manual prop and provide load buttons through the next slot.

    -