From 6221b518439705e15517e7ab972327f4cd62536f Mon Sep 17 00:00:00 2001 From: charrafimed Date: Mon, 12 Jan 2026 08:39:44 +0100 Subject: [PATCH 01/40] wip --- components/pagination/usage.md | 7 +++++++ components/table/usage.md | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 components/pagination/usage.md create mode 100644 components/table/usage.md diff --git a/components/pagination/usage.md b/components/pagination/usage.md new file mode 100644 index 0000000..ecd0844 --- /dev/null +++ b/components/pagination/usage.md @@ -0,0 +1,7 @@ +--- +name: 'pagination' +--- + +# Pagination Component + +## Introduction diff --git a/components/table/usage.md b/components/table/usage.md new file mode 100644 index 0000000..57424ce --- /dev/null +++ b/components/table/usage.md @@ -0,0 +1,7 @@ +--- +name: 'table' +--- + +# Table Component + +## Introduction From 683bbf7d07d7a4b03e04497c4dbe8c1210e04535 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Mon, 12 Jan 2026 13:03:35 +0100 Subject: [PATCH 02/40] wip on skeleton docs --- components/skeleton/usage.md | 357 +++++++++++++++++++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100644 components/skeleton/usage.md diff --git a/components/skeleton/usage.md b/components/skeleton/usage.md new file mode 100644 index 0000000..0adbf35 --- /dev/null +++ b/components/skeleton/usage.md @@ -0,0 +1,357 @@ +--- +name: skeleton +--- + +## Introduction + +The `Skeleton` component provides **loading placeholders** that mimic the structure of your content while data is being fetched. It offers multiple animation styles and a flexible API for creating custom loading states that match your UI perfectly. + +## Installation + +Use the [sheaf artisan command](/docs/guides/cli-installation#content-component-management) to install the `skeleton` component easily: + +```bash +php artisan sheaf:install skeleton +``` + +> Once installed, you can use the `` and `` components in any Blade view. + +## Usage + +@blade + +
+ + + + +
+
+@endblade + +### Basic Skeleton + +The most basic usage is a simple placeholder with custom dimensions: + +```html + + + +``` + +### Animation Variants + +Choose from different animation styles to match your design preference: + +@blade + +
+
+

Pulse (default)

+ +
+ +
+

Wave

+ +
+ +
+

Glow (wave + pulse)

+ +
+ +
+

None

+ +
+
+
+@endblade + +```html + + + + + + + + + + + +``` + +### Text Skeleton + +Use the `skeleton.text` component for text line placeholders with predefined heights: + +@blade + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+@endblade + +```html + + + + + + + + + + + + + + + +``` + +### Card Skeleton + +Create complex loading states by combining skeleton components: + +@blade + +
+
+ +
+ + +
+
+
+ + +
+ +
+ + +
+
+
+@endblade + +```html +
+ +
+ +
+ + +
+
+ + +
+ + +
+ + + + + +
+ + +
+
+``` + +### List Skeleton + +Perfect for loading lists or feeds: + +@blade + +
+ @foreach(range(1, 4) as $i) +
+ +
+ + +
+
+ @endforeach +
+
+@endblade + +```html +
+ @foreach(range(1, 5) as $i) +
+ +
+ + +
+
+ @endforeach +
+``` + +### Grid Skeleton + +For card grids and gallery layouts: + +@blade + +
+ @foreach(range(1, 6) as $i) +
+ + + +
+ @endforeach +
+
+@endblade + +```html +
+ @foreach(range(1, 6) as $i) +
+ + + +
+ @endforeach +
+``` + +### Table Skeleton + +Use with tables and data grids: + +@blade + +
+ + + @foreach(range(1, 5) as $i) + + + + + + + @endforeach + +
+
+
+@endblade + +```html + + + @foreach(range(1, 5) as $i) + + + + + + + @endforeach + +
+``` + +## Conventions + +### Conditional Loading with Livewire + +The most common pattern is to show skeletons while loading data: + +```html +@if($loading) +
+ @foreach(range(1, 5) as $i) +
+ +
+ + +
+
+ @endforeach +
+@else +
+ @foreach($users as $user) + + @endforeach +
+@endif +``` + +### Matching Content Structure + +For the best UX, make your skeleton match the actual content structure as closely as possible: + +```html +{{-- This skeleton matches a product card --}} +
+ +
+ + + +
+ + +
+
+
+``` + +### Component Props + +#### Skeleton Component + +| Prop Name | Type | Default | Required | Description | +| ---------- | ------ | -------- | -------- | -------------------------------------------------------- | +| `animate` | string | `'glow'` | No | Animation style: `'pulse'`, `'wave'`, `'glow'`, `'none'` | + +#### Skeleton Text Component + +| Prop Name | Type | Default | Required | Description | +| ---------- | ------ | --------- | -------- | -------------------------------------------------------- | +| `animate` | string | `'glow'` | No | Animation style: `'pulse'`, `'wave'`, `'glow'`, `'none'` | +| `size` | string | `'base'` | No | Text size: `'sm'`, `'base'`, `'lg'`, `'xl'` | + +## Component Structure + +The skeleton component consists of: + +- **Base Component**: `` - Generic skeleton placeholder +- **Text Component**: `` - Text line placeholder with size variants +- **Abstract Component**: `` - Internal animation logic (not for direct use) From e7be8ed6726055767b469e9a6c30a2bb077263dd Mon Sep 17 00:00:00 2001 From: charrafimed Date: Mon, 12 Jan 2026 15:09:17 +0100 Subject: [PATCH 03/40] complete skeleton docs --- components/skeleton/usage.md | 246 +++++++++++++++-------------------- 1 file changed, 102 insertions(+), 144 deletions(-) diff --git a/components/skeleton/usage.md b/components/skeleton/usage.md index 0adbf35..9855b3a 100644 --- a/components/skeleton/usage.md +++ b/components/skeleton/usage.md @@ -20,7 +20,7 @@ php artisan sheaf:install skeleton @blade -
+
@@ -33,215 +33,204 @@ php artisan sheaf:install skeleton The most basic usage is a simple placeholder with custom dimensions: -```html +```blade ``` -### Animation Variants +## Animation Variants Choose from different animation styles to match your design preference: @blade -
+
-

Pulse (default)

- +

Pulse

+
-

Wave

- +
-

Glow (wave + pulse)

- +
-

None

- +
@endblade -```html - - +```blade + + - + - - + + - + +``` + +## Animation Speed + +@blade + +
+
+

Slow

+ +
+
+

Normal

+ +
+
+

Dast

+ +
+
+
+@endblade + +```blade + + + + + + + + ``` -### Text Skeleton +## Text Skeleton Use the `skeleton.text` component for text line placeholders with predefined heights: @blade -
+
- - +
-
-
-
@endblade -```html +```blade - - - - ``` +## Examples + + ### Card Skeleton Create complex loading states by combining skeleton components: @blade -
+
- +
- - + +
- - + +
- +
- - + +
@endblade -```html +```blade
- +
- - + +
- - + +
- +
- - + +
``` - -### List Skeleton - -Perfect for loading lists or feeds: - -@blade - -
- @foreach(range(1, 4) as $i) -
- -
- - -
-
- @endforeach -
-
-@endblade - -```html -
- @foreach(range(1, 5) as $i) -
- -
- - -
-
- @endforeach -
-``` - ### Grid Skeleton For card grids and gallery layouts: @blade -
+
@foreach(range(1, 6) as $i)
- - - + + +
@endforeach
@endblade -```html +```blade
@foreach(range(1, 6) as $i)
- - - + + +
@endforeach
@@ -253,15 +242,23 @@ Use with tables and data grids: @blade -
+
+ + + + + + + + @foreach(range(1, 5) as $i) - + @endforeach @@ -270,70 +267,31 @@ Use with tables and data grids: @endblade -```html +```blade
#NumberStatusPunishments
+ + + + + + + + @foreach(range(1, 5) as $i) - + @endforeach
#NumberStatusPunishments
``` -## Conventions - -### Conditional Loading with Livewire - -The most common pattern is to show skeletons while loading data: - -```html -@if($loading) -
- @foreach(range(1, 5) as $i) -
- -
- - -
-
- @endforeach -
-@else -
- @foreach($users as $user) - - @endforeach -
-@endif -``` - -### Matching Content Structure - -For the best UX, make your skeleton match the actual content structure as closely as possible: - -```html -{{-- This skeleton matches a product card --}} -
- -
- - - -
- - -
-
-
-``` -### Component Props +## Component Props #### Skeleton Component From fd4a0d2ad5ca0740b9d8473453e60f7d65daeec9 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Wed, 14 Jan 2026 14:58:48 +0100 Subject: [PATCH 04/40] add empty state component docs --- components/empty/usage.md | 461 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 461 insertions(+) create mode 100644 components/empty/usage.md diff --git a/components/empty/usage.md b/components/empty/usage.md new file mode 100644 index 0000000..d3eac2e --- /dev/null +++ b/components/empty/usage.md @@ -0,0 +1,461 @@ +--- +name: empty +--- + +## Introduction + +The `Empty` component provides **beautiful empty state placeholders** for when there's no data to display. It offers multiple variants and a flexible composition API for creating meaningful empty states that guide users toward the next action. + +## Installation + +Use the [sheaf artisan command](/docs/guides/cli-installation#content-component-management) to install the `empty` component easily: + +```bash +php artisan sheaf:install empty +``` + +> Once installed, you can use the ``, ``, and `` components in any Blade view. + +## Usage + +@blade + +
+ + + + + + No results found + + Try adjusting your filters or create a new item. + + + +
+
+@endblade + +### Basic Empty State + +The most basic usage combines media (icon/image) with descriptive content: + +```blade + + + + + + + No messages + + Your inbox is empty. Start a conversation! + + + +``` + +## Variants + +Choose from different visual styles to match your design: + +@blade + +
+
+

Default

+ + + + + + No items + Get started by creating your first item. + + +
+ +
+

Bordered

+ + + + + + No items + Get started by creating your first item. + + +
+ +
+

Background

+ + + + + + No items + Get started by creating your first item. + + +
+
+
+@endblade + +```blade + + + + + + + + + + + + + + +``` + +## Media Variants + +Use different media styles to emphasize your empty state: + +@blade + +
+ + + + + + Icon Media + Simple icon display + + + + + + + + + Icon Variant + With background circle + + +
+
+@endblade + +```blade + + + + + + + + + +``` + +## Examples + +### With Action Button + +Guide users to take action with a prominent button: + +@blade + +
+ + + + + + + No documents + + You haven't created any documents yet. + + + + Create document + + + +
+
+@endblade + +```blade + + + + + + + No documents + + You haven't created any documents yet. + + + + Create document + + + +``` + +### With Avatar Group + +Show team or user context in your empty state: + +@blade + +
+ + + + + + +1 + + + + + The Team Above + + Start by adding your first item. + + +
+ Create + Learn more +
+
+
+
+
+@endblade + +```blade + + + + + + +3 + + + + + The Team Above + + Start by adding your first item. + + +
+ Create + Learn more +
+
+
+``` + +### Search Results Empty State + +Handle empty search results with helpful messaging: + +@blade + +
+ + + + + + + No results found + + We couldn't find anything matching "your search". Try different keywords. + + + + Clear search + + + +
+
+@endblade + +```blade + + + + + + + No results found + + We couldn't find anything matching "{{ $searchQuery }}". Try different keywords. + + + + Clear search + + + +``` + +### In Table Context + +Use empty states within tables for better UX: + +@blade + +
+ + + + + + + + + + + + + +
NameStatusDate
+ + + + + + No data available + + Start by adding your first entry. + + + +
+
+
+@endblade + +```blade + + + @forelse ($items as $item) + + + + @empty + + + + + + + + No data available + + Start by adding your first entry. + + + + + + @endforelse + + +``` + +### Multiple Actions + +Provide users with multiple paths forward: + +@blade + +
+ + + + + + + No projects yet + + Create your first project or import an existing one. + + +
+ New project + Import +
+
+
+
+
+@endblade + +```blade + + + + + + + No projects yet + + Create your first project or import an existing one. + + +
+ New project + Import +
+
+
+``` + +## Component Props + +#### Empty Component + +| Prop Name | Type | Default | Required | Description | +| --------- | ------ | ----------- | -------- | ------------------------------------------------- | +| `variant` | string | `'default'` | No | Visual style: `'default'`, `'bordered'`, `'background'` | + +#### Empty Media Component + +| Prop Name | Type | Default | Required | Description | +| --------- | ------ | ----------- | -------- | ------------------------------------------------- | +| `variant` | string | `'default'` | No | Media style: `'default'`, `'icon'` | + +#### Empty Contents Component + +The contents component accepts any content and has no specific props beyond standard attributes. + +## Component Structure + +The empty component consists of: + +- **Container**: `` - Main wrapper with variant styling +- **Media**: `` - Icon, image, or avatar display area +- **Contents**: `` - Text, buttons, and action elements + +## Best Practices + +1. **Be Helpful**: Explain why the state is empty and what users can do next +2. **Stay Positive**: Use encouraging language rather than negative phrasing +3. **Provide Action**: Include a clear call-to-action when appropriate +4. **Match Context**: Use different messages for "no data" vs "no search results" +5. **Keep it Simple**: Don't overwhelm users with too many options \ No newline at end of file From 8a6e973ec3ce1e5b0a8ff130408cb18d8cf27fe9 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Wed, 14 Jan 2026 15:31:30 +0100 Subject: [PATCH 05/40] wip on empty docs --- components/empty/usage.md | 202 +++++++++++++++++--------------------- 1 file changed, 92 insertions(+), 110 deletions(-) diff --git a/components/empty/usage.md b/components/empty/usage.md index d3eac2e..57baa1c 100644 --- a/components/empty/usage.md +++ b/components/empty/usage.md @@ -4,7 +4,7 @@ name: empty ## Introduction -The `Empty` component provides **beautiful empty state placeholders** for when there's no data to display. It offers multiple variants and a flexible composition API for creating meaningful empty states that guide users toward the next action. +The `Empty` component provides **beautiful empty state placeholders** for when there's no data to display. It offers a flexible composition API for creating meaningful empty states that guide users toward the next action. ## Installation @@ -19,12 +19,13 @@ php artisan sheaf:install empty ## Usage @blade - +
+ No results found @@ -55,96 +56,82 @@ The most basic usage combines media (icon/image) with descriptive content: ``` -## Variants +## Styling Variants -Choose from different visual styles to match your design: +Customize the appearance using Tailwind classes: -@blade - -
-
-

Default

- - - - - - No items - Get started by creating your first item. - - -
- -
-

Bordered

- - - - - - No items - Get started by creating your first item. - - -
+### Bordered -
-

Background

- - - - - - No items - Get started by creating your first item. - - -
+@blade + +
+ + + + + + No items + Get started by creating your first item. + +
@endblade ```blade - - + +``` - - - - +### Background - - +@blade + +
+ + + + + + No items + Get started by creating your first item. + + +
+
+@endblade + +```blade + ``` -## Media Variants +## Media Styling -Use different media styles to emphasize your empty state: +Customize the media area with Tailwind classes: @blade - +
- + - Icon Media - Simple icon display + Simple Icon + Default icon display - - - + + + - Icon Variant - With background circle + Icon with Circle + Circular background
@@ -152,13 +139,13 @@ Use different media styles to emphasize your empty state: @endblade ```blade - + - - + + ``` @@ -170,10 +157,10 @@ Use different media styles to emphasize your empty state: Guide users to take action with a prominent button: @blade - +
- - + + @@ -193,8 +180,8 @@ Guide users to take action with a prominent button: @endblade ```blade - - + + @@ -216,9 +203,9 @@ Guide users to take action with a prominent button: Show team or user context in your empty state: @blade - +
- + @@ -244,7 +231,7 @@ Show team or user context in your empty state: @endblade ```blade - + @@ -272,10 +259,10 @@ Show team or user context in your empty state: Handle empty search results with helpful messaging: @blade - +
- + @@ -296,7 +283,7 @@ Handle empty search results with helpful messaging: ```blade - + @@ -318,7 +305,7 @@ Handle empty search results with helpful messaging: Use empty states within tables for better UX: @blade - +
@@ -332,7 +319,7 @@ Use empty states within tables for better UX:
- + @@ -361,7 +348,7 @@ Use empty states within tables for better UX: - + @@ -383,10 +370,10 @@ Use empty states within tables for better UX: Provide users with multiple paths forward: @blade - +
- - + + @@ -407,8 +394,8 @@ Provide users with multiple paths forward: @endblade ```blade - - + + @@ -426,36 +413,31 @@ Provide users with multiple paths forward: ``` -## Component Props - -#### Empty Component +## Common Styling Patterns -| Prop Name | Type | Default | Required | Description | -| --------- | ------ | ----------- | -------- | ------------------------------------------------- | -| `variant` | string | `'default'` | No | Visual style: `'default'`, `'bordered'`, `'background'` | +Here are some frequently used styling combinations: -#### Empty Media Component - -| Prop Name | Type | Default | Required | Description | -| --------- | ------ | ----------- | -------- | ------------------------------------------------- | -| `variant` | string | `'default'` | No | Media style: `'default'`, `'icon'` | - -#### Empty Contents Component +```blade + + -The contents component accepts any content and has no specific props beyond standard attributes. + + -## Component Structure + + -The empty component consists of: + + -- **Container**: `` - Main wrapper with variant styling -- **Media**: `` - Icon, image, or avatar display area -- **Contents**: `` - Text, buttons, and action elements + + -## Best Practices + + -1. **Be Helpful**: Explain why the state is empty and what users can do next -2. **Stay Positive**: Use encouraging language rather than negative phrasing -3. **Provide Action**: Include a clear call-to-action when appropriate -4. **Match Context**: Use different messages for "no data" vs "no search results" -5. **Keep it Simple**: Don't overwhelm users with too many options \ No newline at end of file + + + + +``` \ No newline at end of file From 759549df263f36533cacc910695a98942e45897e Mon Sep 17 00:00:00 2001 From: charrafimed Date: Wed, 14 Jan 2026 15:50:14 +0100 Subject: [PATCH 06/40] complete the empty component docs --- components/empty/usage.md | 113 ++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 52 deletions(-) diff --git a/components/empty/usage.md b/components/empty/usage.md index 57baa1c..0a010e1 100644 --- a/components/empty/usage.md +++ b/components/empty/usage.md @@ -65,7 +65,7 @@ Customize the appearance using Tailwind classes: @blade
- + @@ -79,7 +79,7 @@ Customize the appearance using Tailwind classes: @endblade ```blade - + ``` @@ -87,9 +87,9 @@ Customize the appearance using Tailwind classes: ### Background @blade - +
- + @@ -103,7 +103,7 @@ Customize the appearance using Tailwind classes: @endblade ```blade - + ``` @@ -115,7 +115,7 @@ Customize the media area with Tailwind classes: @blade
- + @@ -125,7 +125,7 @@ Customize the media area with Tailwind classes: - + @@ -159,7 +159,7 @@ Guide users to take action with a prominent button: @blade
- + @@ -180,7 +180,7 @@ Guide users to take action with a prominent button: @endblade ```blade - + @@ -205,7 +205,7 @@ Show team or user context in your empty state: @blade
- + @@ -231,7 +231,7 @@ Show team or user context in your empty state: @endblade ```blade - + @@ -265,10 +265,10 @@ Handle empty search results with helpful messaging: - - + + No results found - + We couldn't find anything matching "your search". Try different keywords. @@ -289,7 +289,7 @@ Handle empty search results with helpful messaging: No results found - + We couldn't find anything matching "{{ $searchQuery }}". Try different keywords. @@ -306,39 +306,49 @@ Use empty states within tables for better UX: @blade -
- - - - - - - - - - - - - -
NameStatusDate
- - - - - - No data available - - Start by adding your first entry. - - - -
+
+ + + + + name + + + type + + + created at + + + description + + + + + + + + + + + No data available + + Start by adding your first entry. + + + + + +
@endblade ```blade + @forelse ($items as $item) @@ -372,17 +382,16 @@ Provide users with multiple paths forward: @blade
- + - - + + No projects yet Create your first project or import an existing one. -
New project Import @@ -394,7 +403,7 @@ Provide users with multiple paths forward: @endblade ```blade - + @@ -419,19 +428,19 @@ Here are some frequently used styling combinations: ```blade - + - + - + - + - + From 2e7975be2dc3d7d890e3aa6885220d709cf74e75 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Wed, 14 Jan 2026 15:53:05 +0100 Subject: [PATCH 07/40] wip --- components/empty/usage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/empty/usage.md b/components/empty/usage.md index 0a010e1..a9238b2 100644 --- a/components/empty/usage.md +++ b/components/empty/usage.md @@ -217,7 +217,7 @@ Show team or user context in your empty state: The Team Above - Start by adding your first item. + Start by contacting the team.
@@ -243,7 +243,7 @@ Show team or user context in your empty state: The Team Above - Start by adding your first item. + Start by contacting the team.
From 8310497480ef6574d8d266e2175396cf97fe4907 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Sat, 17 Jan 2026 23:50:05 +0100 Subject: [PATCH 08/40] wip --- components/empty/usage.md | 3 +- components/table/usage.md | 1999 +++++++++++++++++++++++++++++++++++++ 2 files changed, 2000 insertions(+), 2 deletions(-) diff --git a/components/empty/usage.md b/components/empty/usage.md index a9238b2..47ccbeb 100644 --- a/components/empty/usage.md +++ b/components/empty/usage.md @@ -213,13 +213,12 @@ Show team or user context in your empty state: +1 - + The Team Above Start by contacting the team. -
Create Learn more diff --git a/components/table/usage.md b/components/table/usage.md index 57424ce..768e0a8 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -5,3 +5,2002 @@ name: 'table' # Table Component ## Introduction + +The `table` component provides a powerful, composable system for building feature-rich data tables. Built with Livewire and Alpine.js, it offers pagination, sorting, searching, selection, bulk actions, column visibility controls, drag-and-drop reordering, and more—all with excellent accessibility and a clean, modern design. + +Unlike monolithic table libraries, our approach uses **composable traits** on the backend and **slot-based components** on the frontend, giving you complete control over your table's structure and behavior while maintaining clean, reusable code. + +## Installation + +Use the [sheaf artisan command](/docs/guides/cli-installation#content-component-management) to install the `table` component: + +```bash +php artisan sheaf:install table +``` + +## Basic Static Table + +Let's start with a simple static table without any dynamic features. + +@blade + + + + + Name + Email + Role + + + + + + Alice Johnson + alice@example.com + Admin + + + Bob Smith + bob@example.com + Editor + + + Carol White + carol@example.com + Viewer + + + + +@endblade + +```blade + + + + Name + Email + Role + + + + + + Alice Johnson + alice@example.com + Admin + + + + +``` + +## Pagination + +Add pagination to your table by passing a Laravel paginator and enabling the pagination feature. + +### Creating the Livewire Component + +First, create a Livewire component that uses the `WithPagination` trait: + +```php +paginate($this->perPage); + + return view('livewire.users-table', [ + 'users' => $users, + ]); + } +} +``` + +### The View + +```blade +
+ + + + Name + Email + Role + + + + + @foreach ($users as $user) + + {{ $user->name }} + {{ $user->email }} + {{ $user->role }} + + @endforeach + + +
+``` + +### Customizing Items Per Page + +The `WithPagination` trait includes a `$perPage` property that defaults to 15. You can override it: + +```php +use WithPagination; + +public int $perPage = 25; +``` + +### Pagination Variants + +Control the pagination appearance with the `pagination:variant` attribute: + +```blade + + + +``` + +Available variants: +- `full` - Shows page numbers, previous/next (default) +- `simple` - Shows only previous/next buttons +- `compact` - Minimal pagination controls + +## Feature Guides + +### Stickiness + +Make columns or headers stick to the viewport while scrolling. + +#### Sticky Header + +Keep the header visible while scrolling through long tables: + +@blade + + + + + Product + Price + Stock + + + + + @for ($i = 1; $i <= 20; $i++) + + Product {{ $i }} + ${{ rand(10, 100) }} + {{ rand(0, 50) }} + + @endfor + + + +@endblade + +```blade + + + + Product + Price + + + + + +``` + +#### Sticky Column + +Make the first column stick when scrolling horizontally: + +@blade + + + + + + Name + + Email + Department + Location + Start Date + + + + + + + Alice Johnson + + alice@company.com + Engineering + San Francisco, CA + 2023-01-15 + + + + Bob Smith + + bob@company.com + Marketing + New York, NY + 2022-08-20 + + + + +@endblade + +```blade + + + + Name + + + + + + + + + Alice Johnson + + + + +``` + +> **Note:** When using sticky columns, always apply a background color to prevent content overlap during scrolling. + +### Add Sorting + +Enable column sorting with visual indicators and flexible behavior. + +#### Step 1: Add the Trait + +Include the `WithSorting` trait in your Livewire component: + +```php +when(filled($this->sortBy), function ($query) { + return $this->applySorting($query); + }) + ->paginate($this->perPage); + + return view('livewire.users-table', [ + 'users' => $users, + ]); + } + + protected function sortableColumns(): array + { + return ['name', 'email', 'created_at']; + } +} +``` + +#### Step 2: Update the View + +Mark columns as sortable and pass the current sort state: + +```blade + + + + Name + + + + Email + + + + Created At + + + +``` + +#### Sorting Variants + +The table supports two sorting variants: `default` (click to toggle) and `dropdown` (menu-based). + +**Default Variant:** + +@blade + + + + + + Product + + + Price + + + + + + Widget A + $29.99 + + + + +@endblade + +```blade + + Name + +``` + +**Dropdown Variant:** + +@blade + + + + + + Product + + + Price + + + + + + Widget A + $29.99 + + + + +@endblade + +```blade + + Name + +``` + +The dropdown variant provides "Sort Ascending", "Sort Descending", and "Clear Sort" options. + +#### How It Works + +The `WithSorting` trait provides: + +- `sortByColumn($column, $dir = null)` - Handles sort toggling +- `clearSorting()` - Resets sorting state +- `applySorting($query)` - Applies sort to the query +- `sortableColumns()` - Whitelist of sortable columns + +Clicking a sortable header cycles through: **asc → desc → no sort**. + +### Add Search + +Enable real-time search across your table data. + +#### Step 1: Add the Trait + +Include the `WithSearch` trait: + +```php +when(filled($this->searchQuery), function ($query) { + return $this->applySearch($query); + }) + ->paginate($this->perPage); + + return view('livewire.users-table', [ + 'users' => $users, + ]); + } + + protected function applySearch($query) + { + $search = str_replace( + ['\\', '%', '_'], + ['\\\\', '\\%', '\\_'], + $this->searchQuery + ); + + return $query->where(function($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%"); + }); + } +} +``` + +> **Security Note:** Always escape LIKE wildcards (`%`, `_`) and wrap OR conditions in a closure to prevent SQL logic bugs. + +#### Step 2: Add the Search Input + +Use the table's `top` slot to add a search field: + +```blade + + +
+ +
+
+ + +
+``` + +#### Optimizing Search Performance + +**Use Debouncing:** + +```blade +wire:model.live.debounce.300ms="searchQuery" +``` + +This reduces server requests by waiting 300ms after the user stops typing. + +**Use Model Scopes:** + +Instead of putting search logic in the component, create a reusable scope: + +```php +// App/Models/User.php +public function scopeSearch($query, string $search) +{ + $search = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $search); + + return $query->where(function($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%"); + }); +} + +// In your component +protected function applySearch($query) +{ + return $query->search($this->searchQuery); +} +``` + +**Use Full-Text Search for Large Datasets:** + +```php +protected function applySearch($query) +{ + return $query->whereFullText(['name', 'email'], $this->searchQuery); +} +``` + +### Handle Empty States + +Provide helpful feedback when no results are found. + +@blade + + + + + Name + Email + + + + + + + + + + + +

No users found

+

+ Try adjusting your search or filters. +

+
+
+
+
+
+
+@endblade + +```blade + + @forelse ($users as $user) + + {{ $user->name }} + {{ $user->email }} + + @empty + + + + + + + +

No users found

+

+ Try adjusting your search or create a new user. +

+
+
+
+ @endforelse +
+``` + +### Add Checkbox Selection + +Enable row selection with checkboxes and a "select all" header. + +#### Step 1: Add the Trait + +Include the `WithSelection` trait: + +```php +paginate($this->perPage); + + // Store visible IDs for "select all" functionality + $this->visibleIds = $users->pluck('id') + ->map(fn ($id) => (string) $id) + ->toArray(); + + return view('livewire.users-table', [ + 'users' => $users, + ]); + } +} +``` + +#### Step 2: Add Checkboxes to the View + +Enable the "check all" header and add checkboxes to rows: + +```blade + + + Name + Email + + + + + @foreach ($users as $user) + + {{ $user->name }} + {{ $user->email }} + + @endforeach + +``` + +#### How It Works + +The checkbox system uses Alpine.js for smooth client-side interactions: + +- Clicking the header checkbox toggles all visible rows +- Individual checkboxes update the selection state +- The header shows an indeterminate state when some (but not all) rows are selected +- Selected IDs are stored in the `$selectedIds` array (as strings) + +**The header checkbox has three states:** +- **Checked** - All visible rows selected +- **Indeterminate** - Some rows selected +- **Unchecked** - No rows selected + +### Bulk Actions (CSV Export Example) + +Perform actions on multiple selected rows. + +#### Step 1: Add the CSV Export Trait + +Include the `CanExportCsv` trait: + +```php +selectedIds)) { + $users = $users->whereIn('id', $this->selectedIds); + } + + return $this->csv($users->get()); + } +} +``` + +#### Step 2: Add Bulk Actions UI + +Use the `top` slot to add bulk action controls: + +```blade + + + +
+ + + + Bulk Actions + + + + + + Export Selected to CSV + + + + Delete Selected + + + +
+ + +
+ +
+
+ + +
+``` + +#### Implementing Delete Action + +Add a delete method with proper validation: + +```php +public function deleteSelected() +{ + // Validate that IDs are valid + $this->validate([ + 'selectedIds' => 'required|array|min:1', + 'selectedIds.*' => 'integer|exists:users,id', + ]); + + // Optional: Add authorization + // Gate::authorize('delete-multiple', User::class); + + $deleted = User::query() + ->whereIn('id', $this->selectedIds) + ->delete(); + + // Clear selection + $this->selectedIds = []; + + // Notify user + $this->dispatch('notify', [ + 'message' => "{$deleted} users deleted successfully", + 'type' => 'success' + ]); +} +``` + +> **Security:** Always validate `selectedIds` to prevent client-side manipulation. Never trust data from the frontend! + +#### Customizing CSV Export + +Override methods in the `CanExportCsv` trait to customize the export: + +```php +protected function getCsvFilename(): string +{ + return 'users_export_' . now()->format('Y-m-d_His') . '.csv'; +} + +protected function getExportableColumns(): array +{ + // Limit which columns are exported + return ['id', 'name', 'email', 'created_at']; +} +``` + +### Add Column Visibility + +Let users show/hide columns dynamically. + +@blade + +
+
+ + + + Columns + + + + + + Name + + + Email + + + Role + + + Status + + + +
+ + + + + + Name + + + Email + + + Role + + + Status + + + + + + + + Alice Johnson + + + alice@example.com + + + Admin + + + Active + + + + +
+
+@endblade + +```blade +
+ + + +
+ + + + Columns + + + + + + Name + + + Email + + + Created At + + + +
+
+ + + + + Name + + + Email + + + Created At + + + + + + @foreach ($users as $user) + + + {{ $user->name }} + + + {{ $user->email }} + + + {{ $user->created_at->format('M d, Y') }} + + + @endforeach + +
+
+``` + +#### Persisting Column Preferences + +Use Alpine's `$persist` plugin to save user preferences across sessions: + +```blade +x-data="{ + visibleCols: $persist(['name', 'email']).as('table-visible-columns') +}" +``` + +This stores the user's column visibility choices in `localStorage`. + +### Add Re-Ordering + +Enable drag-and-drop row reordering using Alpine's sort plugin. + +#### Step 1: Enable Draggable Mode + +Add the `draggable` attribute to your table: + +```blade + + + +``` + +This automatically adds: +- A drag handle column at the start +- Sort functionality via Alpine's `x-sort` directive +- Visual feedback during dragging + +#### Step 2: Handle Reorder Events + +Listen for reorder events in your Livewire component: + +```php + 'handleReorder']; + + public function handleReorder($orderedIds) + { + foreach ($orderedIds as $index => $id) { + User::where('id', $id)->update([ + 'sort_order' => $index + 1 + ]); + } + + $this->dispatch('notify', [ + 'message' => 'Order updated successfully', + 'type' => 'success' + ]); + } + + public function render() + { + $users = User::query() + ->orderBy('sort_order') + ->paginate($this->perPage); + + return view('livewire.users-table', [ + 'users' => $users, + ]); + } +} +``` + +#### Dispatch Reorder Event from Alpine + +Add event dispatch to the sortable container: + +```blade + + + +``` + +> **Note:** Row reordering works best with smaller datasets. For paginated tables, consider reordering within the current page only. + +### Add Filters + +Implement advanced filtering with multiple criteria. + +#### Step 1: Create a Filter Trait + +```php +filters as $field => $value) { + if (filled($value)) { + $query->where($field, $value); + } + } + + return $query; + } + + public function resetFilters() + { + $this->filters = []; + $this->resetPage(); + } + + public function updatedFilters() + { + $this->resetPage(); + } +} +``` + +#### Step 2: Use in Component + +```php +when(filled($this->filters), function ($query) { + return $this->applyFilters($query); + }) + ->paginate($this->perPage); + + return view('livewire.users-table', [ + 'users' => $users, + ]); + } +} +``` + +#### Step 3: Add Filter UI + +```blade + + + +
+ + + + + + + + + + + + + + + Clear Filters + +
+ + +
+ +
+
+ + +
+``` + +#### Advanced Filter Example with Date Ranges + +```php +public function applyFilters($query) +{ + if (filled($this->filters['role'])) { + $query->where('role', $this->filters['role']); + } + + if (filled($this->filters['date_from'])) { + $query->where('created_at', '>=', $this->filters['date_from']); + } + + if (filled($this->filters['date_to'])) { + $query->where('created_at', '<=', $this->filters['date_to']); + } + + return $query; +} +``` + +### Playing with Table Design + +Customize the table's appearance with utility classes and variants. + +#### Bordered Table + +@blade + + + + + Product + Price + Stock + + + + + + Widget A + $29.99 + 45 + + + Widget B + $39.99 + 12 + + + + +@endblade + +```blade + + + +``` + +#### Striped Rows + +@blade + + + + + Name + Email + + + + + + Alice Johnson + alice@example.com + + + Bob Smith + bob@example.com + + + Carol White + carol@example.com + + + + +@endblade + +```blade + + + +``` + +#### Hover Effects + +@blade + + + + + Name + Status + + + + + + Alice Johnson + Active + + + Bob Smith + Active + + + + +@endblade + +```blade + + + +``` + +#### Compact Table + +@blade + + + + + Name + Email + + + + + + Alice Johnson + alice@example.com + + + Bob Smith + bob@example.com + + + + +@endblade + +```blade + + + {{ $user->name }} + +``` + +#### Custom Loading States + +```blade + + +
+ + Loading data... +
+
+ + +
+``` + +## Complete Example + +Here's a full-featured datatable combining all the features: + +```php +baseQuery(); + + if (filled($this->searchQuery)) { + $components = $this->applySearch($components); + } + + if (filled($this->sortBy)) { + $components = $this->applySorting($components); + } + + if (filled($this->selectedIds)) { + $components = $this->applySelection($components); + } + + return $this->csv($components->get()); + } + + public function deleteSelected() + { + $this->validate([ + 'selectedIds' => 'required|array|min:1', + 'selectedIds.*' => 'integer|exists:components,id', + ]); + + $deleted = $this->baseQuery() + ->whereIn('id', $this->selectedIds) + ->delete(); + + $this->selectedIds = []; + + $this->dispatch('notify', [ + 'message' => "{$deleted} components deleted", + 'type' => 'success' + ]); + } + + public function render(): View + { + $components = $this->baseQuery() + ->when(filled($this->sortBy), fn($q) => $this->applySorting($q)) + ->when(filled($this->searchQuery), fn($q) => $this->applySearch($q)) + ->paginate($this->perPage); + + $this->visibleIds = $components->pluck('id') + ->map(fn ($id) => (string) $id) + ->toArray(); + + return view('livewire.components-table', [ + 'components' => $components, + ]); + } + + protected function applySearch($query) + { + $search = str_replace( + ['\\', '%', '_'], + ['\\\\', '\\%', '\\_'], + $this->searchQuery + ); + + return $query->where(function($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('description', 'like', "%{$search}%"); + }); + } + + protected function sortableColumns(): array + { + return ['name', 'type', 'created_at']; + } + + protected function baseQuery() + { + return ComponentModel::query(); + } +} +``` + +```blade +
+ + + +
+ + + + Bulk Actions + + + + + + Export Selected CSV + + + + Delete Selected + + + +
+ + +
+ +
+ + + + + + Columns + + + + + + Name + + + Type + + + Created At + + + Description + + + +
+ + + + + Name + + + + Type + + + + Created At + + + + Description + + + + + + + + @forelse ($components as $component) + + + {{ $component->name }} + + + + {{ $component->type }} + + + + {{ $component->created_at->format('M d, Y') }} + + + + {{ str($component->description)->limit(100) }} + + + + + + + + + + + Edit + + + Delete + + + + + + @empty + + + + + + +

No results found

+

+ Try adjusting your search or create a new component. +

+
+
+
+ @endforelse +
+
+
+``` + +## Component Props + +### Table + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `paginator` | Paginator\|null | `null` | Laravel paginator instance | +| `pagination:variant` | string | `'full'` | Pagination style: `full`, `simple`, `compact` | +| `loading` | slot | `null` | Custom loading indicator | +| `top` | slot | `null` | Content above the table (filters, search, etc.) | +| `footer` | slot | `null` | Content below the table | +| `draggable` | boolean | `false` | Enable drag-and-drop reordering | +| `loadOnPagination` | boolean | `false` | Show loading state during pagination | +| `wire:loading` | boolean | `false` | Enable Livewire loading states | +| `wire:target` | string | `null` | Specific Livewire actions to track | +| `class` | string | `''` | Additional CSS classes | + +### Table.Header + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `sticky` | boolean | `false` | Make header stick to top while scrolling | +| `class` | string | `''` | Additional CSS classes | + +### Table.Head + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `column` | string\|null | `null` | Column name for sorting | +| `sortable` | boolean | `false` | Enable sorting for this column | +| `variant` | string | `'default'` | Sorting UI: `default`, `dropdown` | +| `currentSortBy` | string | `''` | Current sort column | +| `currentSortDir` | string | `''` | Current sort direction | +| `sticky` | boolean | `false` | Make column stick while scrolling horizontally | +| `class` | string | `''` | Additional CSS classes | + +### Table.Row + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `key` | string\|null | `null` | Unique key for Livewire tracking | +| `checkboxId` | string\|int\|null | `null` | ID for checkbox selection | +| `class` | string | `''` | Additional CSS classes | + +### Table.Cell + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `sticky` | boolean | `false` | Make cell stick while scrolling horizontally | +| `class` | string | `''` | Additional CSS classes | + +## Available Traits + +### WithPagination + +```php +use Src\Components\Livewire\Concerns\WithPagination; + +public int $perPage = 15; +``` + +### WithSearch + +```php +use Src\Components\Livewire\Concerns\WithSearch; + +public string $searchQuery = ''; + +protected function applySearch($query) +{ + // Your search logic +} +``` + +### WithSorting + +```php +use Src\Components\Livewire\Concerns\WithSorting; + +public string $sortBy = ''; +public string $sortDir = 'asc'; + +protected function sortableColumns(): array +{ + return ['name', 'email', 'created_at']; +} +``` + +### WithSelection + +```php +use Src\Components\Livewire\Concerns\WithSelection; + +public array $selectedIds = []; +public array $visibleIds = []; +``` + +### CanExportCsv + +```php +use Src\Components\Livewire\Concerns\CanExportCsv; + +#[Renderless] +public function exportToCsv() +{ + return $this->csv($query->get()); +} +``` + +## Tips & Best Practices + +### Performance Optimization + +**1. Use Debouncing for Search** +```blade +wire:model.live.debounce.300ms="searchQuery" +``` + +**2. Add Loading States** +```blade + +``` + +**3. Eager Load Relationships** +```php +$users = User::with('role', 'department') + ->paginate($this->perPage); +``` + +**4. Use Query Scopes** +```php +// Instead of complex component logic +protected function applySearch($query) +{ + return $query->search($this->searchQuery); +} +``` + +### Security Best Practices + +**1. Always Validate Selected IDs** +```php +public function deleteSelected() +{ + $this->validate([ + 'selectedIds' => 'required|array|min:1', + 'selectedIds.*' => 'integer|exists:users,id', + ]); + + // Safe to proceed +} +``` + +**2. Escape Search Queries** +```php +protected function applySearch($query) +{ + $search = str_replace( + ['\\', '%', '_'], + ['\\\\', '\\%', '\\_'], + $this->searchQuery + ); + + return $query->where(function($q) use ($search) { + // Safely use $search here + }); +} +``` + +**3. Whitelist Sortable Columns** +```php +protected function sortableColumns(): array +{ + return ['name', 'email', 'created_at']; +} +``` + +**4. Add Authorization Checks** +```php +public function deleteSelected() +{ + Gate::authorize('delete-multiple', User::class); + + // Delete logic... +} +``` + +### Accessibility + +The table component is built with accessibility in mind: + +- **Keyboard Navigation**: Checkboxes and buttons are fully keyboard accessible +- **Screen Reader Support**: Proper ARIA labels and semantic HTML +- **Focus Management**: Clear focus indicators on interactive elements +- **Sort Indicators**: Visual and semantic indicators for sort state + +### Common Patterns + +**Reset All Filters** +```php +public function resetAll() +{ + $this->searchQuery = ''; + $this->sortBy = ''; + $this->sortDir = 'asc'; + $this->selectedIds = []; + $this->filters = []; + $this->resetPage(); +} +``` + +**Export All vs Selected** +```php +public function export($exportType = 'selected') +{ + $query = $this->baseQuery(); + + if ($exportType === 'selected' && filled($this->selectedIds)) { + $query->whereIn('id', $this->selectedIds); + } + + // Apply current filters/search + if (filled($this->searchQuery)) { + $query = $this->applySearch($query); + } + + return $this->csv($query->get()); +} +``` + +**Per-Page Selector** +```blade + + + + + + +``` + +## Troubleshooting + +### Checkboxes Not Syncing + +Ensure you're setting `visibleIds` in your render method: + +```php +public function render() +{ + $users = User::query()->paginate($this->perPage); + + // This is crucial for checkbox sync + $this->visibleIds = $users->pluck('id') + ->map(fn ($id) => (string) $id) + ->toArray(); + + return view('livewire.users-table', ['users' => $users]); +} +``` + +### Search Not Resetting Pagination + +Make sure the `WithSearch` trait's `updatedSearchQuery` is working: + +```php +public function updatedSearchQuery() +{ + $this->resetPage(); +} +``` + +### Column Visibility Not Persisting + +Install and configure Alpine's persist plugin: + +```js +import persist from '@alpinejs/persist' +Alpine.plugin(persist) +``` + +Then use it in your component: +```blade +x-data="{ visibleCols: $persist(['name']).as('table-cols') }" +``` + +### Sorting Not Working + +1. Check the column name matches your database column +2. Verify the column is in `sortableColumns()` +3. Ensure you're passing `currentSortBy` and `currentSortDir` to the head component + +## Related Components + +- [Pagination](/docs/components/pagination) - Standalone pagination component +- [Dropdown](/docs/components/dropdown) - Used for bulk actions and filters +- [Input](/docs/components/input) - Used for search fields +- [Checkbox](/docs/components/checkbox) - Used for row selection +- [Empty State](/docs/components/empty) - Used when no results found \ No newline at end of file From 0d8a981c93743d53e2ae1a3def7e17eca16c206c Mon Sep 17 00:00:00 2001 From: charrafimed Date: Sun, 18 Jan 2026 00:47:32 +0100 Subject: [PATCH 09/40] wip on data tables docs --- components/table/usage.md | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/components/table/usage.md b/components/table/usage.md index 768e0a8..c1f087c 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -15,7 +15,7 @@ Unlike monolithic table libraries, our approach uses **composable traits** on th Use the [sheaf artisan command](/docs/guides/cli-installation#content-component-management) to install the `table` component: ```bash -php artisan sheaf:install table +php artisan sheaf:install data-table ``` ## Basic Static Table @@ -23,7 +23,7 @@ php artisan sheaf:install table Let's start with a simple static table without any dynamic features. @blade - + @@ -77,11 +77,11 @@ Let's start with a simple static table without any dynamic features. ## Pagination -Add pagination to your table by passing a Laravel paginator and enabling the pagination feature. +Add pagination to your table by passing a Laravel paginator to table component. ### Creating the Livewire Component -First, create a Livewire component that uses the `WithPagination` trait: +First, create a Livewire component that uses the `App\Livewire\Concerns\WithPagination` trait: ```php paginate($this->perPage); + ->paginate(); + // or (if you using length aware paginator with full variant) + // ->paginate($this->perPage); return view('livewire.users-table', [ 'users' => $users, @@ -115,18 +117,14 @@ class UsersTable extends Component - Name - Email - Role + @foreach ($users as $user) - {{ $user->name }} - {{ $user->email }} - {{ $user->role }} + @endforeach @@ -136,13 +134,7 @@ class UsersTable extends Component ### Customizing Items Per Page -The `WithPagination` trait includes a `$perPage` property that defaults to 15. You can override it: - -```php -use WithPagination; - -public int $perPage = 25; -``` +The `App\Livewire\Concerns\WithPagination` trait includes a `$perPage` property that defaults to 15. You can override it in the trait, don't forget the code is yours tweack it as you want. ### Pagination Variants From b5a6cfe87a165bddcb9bdfcfc74232f203cd1c12 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Sun, 18 Jan 2026 15:59:18 +0100 Subject: [PATCH 10/40] pagination docs in progress... --- components/pagination/usage.md | 200 +++++++++++++++++++++++++++++++++ components/table/usage.md | 55 +++------ 2 files changed, 213 insertions(+), 42 deletions(-) diff --git a/components/pagination/usage.md b/components/pagination/usage.md index ecd0844..8048ac0 100644 --- a/components/pagination/usage.md +++ b/components/pagination/usage.md @@ -5,3 +5,203 @@ name: 'pagination' # Pagination Component ## Introduction + +The `pagination` component provides a flexible, accessible navigation system for paginated data. Built on top of Laravel's pagination system, it automatically detects whether you're using simple pagination or length-aware pagination and renders the appropriate interface. With two visual variants and full Livewire integration, it seamlessly handles user navigation through large datasets. + +## Installation + +Use the [sheaf artisan command](/docs/guides/cli-installation#content-component-management) to install the `pagination` component: + +```bash +php artisan sheaf:install pagination +``` + +> Once installed, you can use the component in any livewire component. + + +## Basic Usage + +### With Length-Aware Pagination + +Length-aware pagination provides page numbers and total count, ideal for datasets where users need to know the total number of pages. + +```php +use App\Livewire\Concerns\WithPagination; + +class UsersTable extends Component +{ + use WithPagination; + + public function render() + { + return view('livewire.users-table', [ + 'users' => User::paginate($this->perPage) + ]); + } +} +``` + +```blade + +
+ @foreach($users as $user) +
{{ $user->name }}
+ @endforeach + + +
+``` + +### With Simple Pagination + +Simple pagination only shows Previous/Next buttons without total page count, optimized for large datasets where counting all records would be expensive. + +```php +// In your Livewire component +public function render() +{ + return view('livewire.users-table', [ + 'users' => User::simplePaginate($this->perPage) + ]); +} +``` + +```blade + + +``` + +> **Note:** The component automatically detects whether you're using `paginate()` or `simplePaginate()` and renders the appropriate interface. + +> **Note:** the `perPage` is optional and we need it only when using the length aware pagination with full variant otherwise you can keep it just as a reusable flag across your paginations. + +## Pagination Variants + +### Default Variant + +Compact pagination with minimal UI on the right corner for *simpl* and **length aware** paginations. + +@blade + +@endblade + +### Full Variant + +Comprehensive pagination with item counts, page information, and per-page selector (for length-aware only), and expand the pagination to the full width for the simple pagination. + +**Features:** +- Shows "1-15 of 150" item range +- Displays "Page 1 of 10" +- Includes per-page selector (10, 20, 30, 40, 50) +- First/Last page buttons + +@blade + +@endblade +## Usage with Table Component + +The pagination component integrates seamlessly with the table component. + +### Basic Integration + +```blade + + + + Name + Email + + + + + @foreach($users as $user) + + {{ $user->name }} + {{ $user->email }} + + @endforeach + + +``` + +> **Tip:** When you pass `:paginator` to the table component, it automatically renders pagination at the bottom. No need to add `` separately! + +### Specifying Pagination Variant + +Control the pagination variant through the table component: + +```blade + + + +``` + + +### With Full Variant Per-Page Options + +pass your own `:options` prop to `pagination` component + +```blade + +``` + +or + +``` + + + +``` + +for tables + + + +### Reset Pagination on Filter Change + +```php +use App\Livewire\Concerns\WithPagination; + +class UsersTable extends Component +{ + use WithPagination; + + public string $searchQuery = ''; + + public function updatedSearchQuery() + { + $this->resetPage(); // Reset to page 1 when search changes + } + + public function render() + { + $users = User::query() + ->when($this->searchQuery, fn($q) => + $q->where('name', 'like', "%{$this->searchQuery}%") + ) + ->paginate($this->perPage); + + return view('livewire.users-table', ['users' => $users]); + } +} +``` +## Component Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `paginator` | `Paginator\|LengthAwarePaginator\|CursorPaginator` | `null` | Laravel paginator instance (required) | +| `variant` | `string` | `'default'` | Visual variant: `'default'` or `'full'` | diff --git a/components/table/usage.md b/components/table/usage.md index c1f087c..b942204 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -84,25 +84,16 @@ Add pagination to your table by passing a Laravel paginator to table component. First, create a Livewire component that uses the `App\Livewire\Concerns\WithPagination` trait: ```php -paginate(); + $users = User::query()->paginate(); // or (if you using length aware paginator with full variant) // ->paginate($this->perPage); - return view('livewire.users-table', [ 'users' => $users, ]); @@ -115,19 +106,7 @@ class UsersTable extends Component ```blade
- - - - - - - - @foreach ($users as $user) - - - - @endforeach - +
``` @@ -136,25 +115,17 @@ class UsersTable extends Component The `App\Livewire\Concerns\WithPagination` trait includes a `$perPage` property that defaults to 15. You can override it in the trait, don't forget the code is yours tweack it as you want. -### Pagination Variants - -Control the pagination appearance with the `pagination:variant` attribute: - -```blade - - - -``` - -Available variants: -- `full` - Shows page numbers, previous/next (default) -- `simple` - Shows only previous/next buttons -- `compact` - Minimal pagination controls +### More About Pagination ? +@blade + +@endblade ## Feature Guides +in this guide I am going to walk throught using this data-table component with bunch of others to create a stunning data tables, I am going to list list of math theorems and thier founding year and the mathematiciens behind them..., ### Stickiness From 4c4db82f3e62d86f752d99cec967f8e5f05fb57c Mon Sep 17 00:00:00 2001 From: charrafimed Date: Sun, 18 Jan 2026 17:29:59 +0100 Subject: [PATCH 11/40] finish pagination docs --- components/pagination/usage.md | 62 +++++++++++++++++----------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/components/pagination/usage.md b/components/pagination/usage.md index 8048ac0..b593455 100644 --- a/components/pagination/usage.md +++ b/components/pagination/usage.md @@ -11,22 +11,27 @@ The `pagination` component provides a flexible, accessible navigation system for ## Installation Use the [sheaf artisan command](/docs/guides/cli-installation#content-component-management) to install the `pagination` component: - ```bash php artisan sheaf:install pagination ``` -> Once installed, you can use the component in any livewire component. - +> Once installed, you can use the `` component in any Livewire component. ## Basic Usage +@blade + +@endblade + ### With Length-Aware Pagination Length-aware pagination provides page numbers and total count, ideal for datasets where users need to know the total number of pages. - ```php -use App\Livewire\Concerns\WithPagination; +use Src\Components\Livewire\Concerns\WithPagination; class UsersTable extends Component { @@ -40,7 +45,6 @@ class UsersTable extends Component } } ``` - ```blade
@@ -55,9 +59,7 @@ class UsersTable extends Component ### With Simple Pagination Simple pagination only shows Previous/Next buttons without total page count, optimized for large datasets where counting all records would be expensive. - ```php -// In your Livewire component public function render() { return view('livewire.users-table', [ @@ -65,7 +67,6 @@ public function render() ]); } ``` - ```blade @@ -73,45 +74,43 @@ public function render() > **Note:** The component automatically detects whether you're using `paginate()` or `simplePaginate()` and renders the appropriate interface. -> **Note:** the `perPage` is optional and we need it only when using the length aware pagination with full variant otherwise you can keep it just as a reusable flag across your paginations. - ## Pagination Variants ### Default Variant -Compact pagination with minimal UI on the right corner for *simpl* and **length aware** paginations. +Compact pagination with minimal UI on the right corner for **simple** and **length-aware** paginations. No loading indicators. @blade @endblade ### Full Variant -Comprehensive pagination with item counts, page information, and per-page selector (for length-aware only), and expand the pagination to the full width for the simple pagination. +Comprehensive pagination with item counts, page information, and per-page selector (for length-aware only). Expands to full width for simple pagination. Includes loading indicators. **Features:** - Shows "1-15 of 150" item range - Displays "Page 1 of 10" -- Includes per-page selector (10, 20, 30, 40, 50) +- Includes per-page selector (10, 20, 30...) - First/Last page buttons @blade @endblade + ## Usage with Table Component The pagination component integrates seamlessly with the table component. ### Basic Integration - ```blade @@ -137,7 +136,6 @@ The pagination component integrates seamlessly with the table component. ### Specifying Pagination Variant Control the pagination variant through the table component: - ```blade ``` +## Customization -### With Full Variant Per-Page Options - -pass your own `:options` prop to `pagination` component +### Per-Page Options +Customize the available per-page options (default: 10, 20, 30, 40, 50): ```blade - -``` - -or + + -``` + ``` -for tables - - +> **Note:** The `$perPage` property defaults to 15. It's only interactive (user-changeable) when using length-aware pagination with the `full` variant, which includes a per-page selector. ### Reset Pagination on Filter Change - ```php -use App\Livewire\Concerns\WithPagination; +use Src\Components\Livewire\Concerns\WithPagination; class UsersTable extends Component { @@ -199,9 +197,11 @@ class UsersTable extends Component } } ``` + ## Component Props | Prop | Type | Default | Description | |------|------|---------|-------------| | `paginator` | `Paginator\|LengthAwarePaginator\|CursorPaginator` | `null` | Laravel paginator instance (required) | | `variant` | `string` | `'default'` | Visual variant: `'default'` or `'full'` | +| `options` | `array` | `[10, 20, 30, 40, 50]` | Per-page options (full variant only) | \ No newline at end of file From 0f130a15c3162893fea3eff3e4ae2ee4708aa711 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Wed, 21 Jan 2026 16:20:19 +0100 Subject: [PATCH 12/40] document new dropdown features: checkbox, radio variants, readOnly items and resetOnClick prop --- components/dropdown/usage.md | 290 ++++++++++++++++++++++++++++++++++- components/table/usage.md | 20 +-- 2 files changed, 293 insertions(+), 17 deletions(-) diff --git a/components/dropdown/usage.md b/components/dropdown/usage.md index 8b6898b..ccd6bc1 100644 --- a/components/dropdown/usage.md +++ b/components/dropdown/usage.md @@ -129,7 +129,7 @@ Add visual clarity with icons for better user experience. ``` - +``` ### Linked Items @@ -768,6 +768,284 @@ Combine all features for sophisticated dropdown menus. Here's the section to add to your dropdown documentation: +## Checkbox Variant + +The dropdown now supports checkbox items for multi-selection scenarios. When enabled, items can be toggled on/off independently. + +### Basic Checkbox Usage + +@blade + + + + + Filters + + + + + + Active Items + + + + Archived Items + + + + Deleted Items + + + + +@endblade + +```blade + + + + Filters + + + + + + Active Items + + + + Archived Items + + + + Deleted Items + + + +``` + +### Custom Checkbox Variant + +Use `checkboxVariant` for a more prominent checkbox style with visible checkboxes: + +@blade + + + + + Column Visibility + + + + + + Hidden Columns + + + + + + Probability + + + + Difficulty + + + + Status + + + + +@endblade + +```blade + + + + Column Visibility + + + + + + Hidden Columns + + + + + + Probability + + + + Difficulty + + + + Status + + + +``` + +**Differences:** +- `checkbox`: Uses a minimal checkmark icon that appears when selected +- `checkboxVariant`: Shows an actual checkbox UI element with background and border + +## Radio Variant + +For single-selection scenarios, use the radio variant: + +@blade + + + + + Sort By + + + + + + Name + + + + Date + + + + Size + + + + +@endblade + +```blade + + + + Sort By + + + + + + Name + + + + Date + + + + Size + + + +``` + +**Note:** Radio items require a `name` attribute to group them together. + +## Read-Only Items (Titles) + +Use `readOnly` items as non-interactive section titles or headers within your dropdown: + +@blade + + + + + Display Options + + + + + + View Settings + + + + + + Grid View + + + + List View + + + + + + Active Filters + + + + + + Show Active Only + + + + Show Archived + + + + +@endblade + +```blade + + + + Display Options + + + + + + View Settings + + + + + + Grid View + + + + List View + + + + + + Active Filters + + + + + + Show Active Only + + + + Show Archived + + + +``` + +**Benefits:** +- Provides visual organization +- Non-interactive, preventing accidental clicks +- Spans full width for clear section separation +- Styled differently from regular items + ## Portal Mode The `portal` prop controls where the dropdown menu is rendered in the DOM. By default (`portal="false"`), the menu renders as a child of the dropdown component. When enabled (`portal="true"`), the menu is teleported to the end of the `` element. @@ -823,6 +1101,7 @@ When `portal="true"`, the dropdown menu loses access to parent CSS custom proper **Note:** The menu remains properly positioned via `x-anchor` regardless of portal usage. + ## Component Props ### Dropdown (Main Component) @@ -832,6 +1111,10 @@ When `portal="true"`, the dropdown menu loses access to parent CSS custom proper | `position` | string | `'bottom-center'` | No | Dropdown positioning: `bottom-center`, `bottom-start`, `bottom-end`, `top-center`, `top-start`, `top-end` | | `portal` | string | `null` | No | teleported dropdown: `portal` prop | | `class` | string | `''` | No | Additional CSS classes | +| `checkbox` | boolean | `false` | No | Enable checkbox mode for multi-selection | +| `radio` | boolean | `false` | No | Enable radio mode for single selection | +| `resetFocus` | boolean | `false` | No | Return focus to trigger button when dropdown closes | +| `checkboxVariant` | boolean | `false` | No | Use prominent checkbox UI (requires `checkbox="true"`) | ### Dropdown Item @@ -845,6 +1128,9 @@ When `portal="true"`, the dropdown menu loses access to parent CSS custom proper | `variant` | string | `'soft'` | No | Visual variant: `soft`, `danger` | | `href` | string | `null` | No | URL for navigation items | | `class` | string | `''` | No | Additional CSS classes | +| `readOnly` | boolean | `false` | No | Makes item non-interactive (useful as section title) | +| `value` | string | `null` | No | Value for checkbox/radio items | +| `name` | string | `null` | No | Name attribute for radio groups | ### Dropdown Group diff --git a/components/table/usage.md b/components/table/usage.md index b942204..c97c421 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -84,21 +84,11 @@ Add pagination to your table by passing a Laravel paginator to table component. First, create a Livewire component that uses the `App\Livewire\Concerns\WithPagination` trait: ```php -// ... -class UsersTable extends Component -{ - use App\Livewire\Concerns\WithPagination; - - public function render() - { - $users = User::query()->paginate(); - // or (if you using length aware paginator with full variant) - // ->paginate($this->perPage); - return view('livewire.users-table', [ - 'users' => $users, - ]); - } -} + use \App\Livewire\Concerns\WithPagination; + // livewire class side + $users = User::query()->paginate(); + // or (if you using length aware paginator with full variant) + // ->paginate($this->perPage); ``` ### The View From d0a093daeca080c1834990962efda6e595b3bd40 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Thu, 22 Jan 2026 17:03:06 +0100 Subject: [PATCH 13/40] wip --- components/otp/usage.md | 27 --- components/table/usage.md | 479 ++++++++++++++++++++++++++++++-------- 2 files changed, 383 insertions(+), 123 deletions(-) diff --git a/components/otp/usage.md b/components/otp/usage.md index 213369a..ea17b7d 100644 --- a/components/otp/usage.md +++ b/components/otp/usage.md @@ -505,30 +505,3 @@ The component includes comprehensive accessibility features: | `otp-complete` | `{ code: string }` | Fired when all inputs are filled | | `otp-clear` | - | Clears all inputs (listen with window) | | `otp-focus` | - | Focuses first empty input (listen with window) | - -## Technical Notes - -### Livewire Integration -- Uses `Livewire.hook('morphed')` to handle DOM diffing -- Refreshes `data-order` attributes after each morph -- Supports both regular and `.live` model bindings -- State synchronization happens automatically - -### Browser Compatibility -- Works across Chrome, Firefox, Safari, and Edge -- Uses `requestAnimationFrame()` for consistent focus behavior -- Firefox-specific workarounds for `select()` timing issues - -### Performance -- Minimal re-renders with smart state management -- Uses `$nextTick()` to prevent race conditions -- Efficient DOM queries with cached input references - -### Edge Cases Handled -- Partial paste content -- Invalid character filtering -- Multi-character input (takes last character) -- Clicking disabled inputs (::after overlay trick) -- Livewire morph attribute stripping -- External state changes (SSR-safe) -- Delete key treated as backspace for consistency diff --git a/components/table/usage.md b/components/table/usage.md index c97c421..c48d79a 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -6,9 +6,9 @@ name: 'table' ## Introduction -The `table` component provides a powerful, composable system for building feature-rich data tables. Built with Livewire and Alpine.js, it offers pagination, sorting, searching, selection, bulk actions, column visibility controls, drag-and-drop reordering, and more—all with excellent accessibility and a clean, modern design. +The `table` component provides a powerful, composable system for building feature-rich data tables. Built with Livewire and Alpine.js, it offers pagination, sorting, searching, selection, bulk actions, column visibility controls, drag-and-drop reordering, and more all with excellent accessibility and a clean, modern design. -Unlike monolithic table libraries, our approach uses **composable traits** on the backend and **slot-based components** on the frontend, giving you complete control over your table's structure and behavior while maintaining clean, reusable code. +Our approach uses **composable traits** on the backend and **slot-based components** on the frontend, giving you complete control over your table's structure and behavior while maintaining clean, reusable code. ## Installation @@ -32,7 +32,6 @@ Let's start with a simple static table without any dynamic features. Role - Alice Johnson @@ -86,7 +85,7 @@ First, create a Livewire component that uses the `App\Livewire\Concerns\WithPagi ```php use \App\Livewire\Concerns\WithPagination; // livewire class side - $users = User::query()->paginate(); + $theorems = User::query()->paginate(); // or (if you using length aware paginator with full variant) // ->paginate($this->perPage); ``` @@ -95,7 +94,7 @@ First, create a Livewire component that uses the `App\Livewire\Concerns\WithPagi ```blade
- +
@@ -114,19 +113,59 @@ The `App\Livewire\Concerns\WithPagination` trait includes a `$perPage` property /> @endblade -## Feature Guides -in this guide I am going to walk throught using this data-table component with bunch of others to create a stunning data tables, I am going to list list of math theorems and thier founding year and the mathematiciens behind them..., +## Sorting +This component includes everything you need to add sorting to your tables. To enable sorting, first add the `App\Livewire\Concerns\WithSorting` trait to your Livewire component like this: + +```php +{~ +use App\Models\User; +use App\Livewire\Concerns\WithSorting; +use App\Livewire\Concerns\WithPagination; +class Theorems extends Component +{ ~} + {+ + use WithSorting;+} + + public function render() + { + $users = User::query() +{+ ->when(filled($this->sortBy), function ($query) { + return $this->applySorting($query); + })+} + ->paginate(); -### Stickiness + return view('livewire.users', [ + 'users' => $users, + ]); + } +} +``` +In your table header view, mark sortable columns and pass the current sort state like this: + +```blade + + name + +``` +- `column` should match your database column name. +- `sortable` marks the column as sortable. +- `currentSortBy` and `currentSortDir` are reactive Livewire properties tracking the current sort state. + +## Stickiness Make columns or headers stick to the viewport while scrolling. -#### Sticky Header +### Sticky Header Keep the header visible while scrolling through long tables: @blade - + @@ -135,7 +174,6 @@ Keep the header visible while scrolling through long tables: Stock - @for ($i = 1; $i <= 20; $i++) @@ -162,14 +200,14 @@ Keep the header visible while scrolling through long tables: ``` -#### Sticky Column +### Sticky Column Make the first column stick when scrolling horizontally: @blade - + Department Location Start Date + End Date - Engineering San Francisco, CA 2023-01-15 + 2029-01-15 Marketing New York, NY 2022-08-20 + 2028-08-20 @@ -240,51 +280,298 @@ Make the first column stick when scrolling horizontally:
``` -> **Note:** When using sticky columns, always apply a background color to prevent content overlap during scrolling. +> **Note:** When using sticky header, columns, always apply a background color to prevent content overlap during scrolling. -### Add Sorting -Enable column sorting with visual indicators and flexible behavior. +## Feature Guides +in this guide I am going to walk throught using this data-table component with bunch of others to create a stunning data tables, I am going to list list of math theorems and thier founding year and the mathematiciens behind them..., -#### Step 1: Add the Trait +### Setup Theorem Model -Include the `WithSorting` trait in your Livewire component: +at first we need a `Theorem` model with (id, name, mathematician, field, year_discovered, difficulty_level, is_proven, statement and applications), we are using sushi for this demo wich our `App\Models\Theorem` looks like this: ```php 'integer', + 'difficulty_level' => 'integer', + 'is_proven' => 'boolean', + ]; + + protected function getRows(): array + { + return [ + ['id' => 1, 'name' => 'Pythagorean Theorem', 'mathematician' => 'Pythagoras', 'field' => 'Geometry', 'year_discovered' => -500, 'difficulty_level' => 2, 'is_proven' => true, 'statement' => 'In a right triangle, a² + b² = c²', 'applications' => 'Architecture, Navigation'], + ['id' => 2, 'name' => 'Fundamental Theorem of Calculus', 'mathematician' => 'Isaac Newton & Gottfried Leibniz', 'field' => 'Analysis', 'year_discovered' => 1666, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Links differentiation and integration', 'applications' => 'Physics, Engineering'], + ['id' => 3, 'name' => 'Fermat\'s Last Theorem', 'mathematician' => 'Andrew Wiles', 'field' => 'Number Theory', 'year_discovered' => 1995, 'difficulty_level' => 10, 'is_proven' => true, 'statement' => 'No three positive integers satisfy aⁿ + bⁿ = cⁿ for n > 2', 'applications' => 'Pure Mathematics'], + ['id' => 4, 'name' => 'Gödel\'s Incompleteness Theorems', 'mathematician' => 'Kurt Gödel', 'field' => 'Logic', 'year_discovered' => 1931, 'difficulty_level' => 9, 'is_proven' => true, 'statement' => 'Any consistent formal system has unprovable truths', 'applications' => 'Computer Science, Philosophy'], + ['id' => 5, 'name' => 'Euler\'s Identity', 'mathematician' => 'Leonhard Euler', 'field' => 'Complex Analysis', 'year_discovered' => 1748, 'difficulty_level' => 6, 'is_proven' => true, 'statement' => 'e^(iπ) + 1 = 0', 'applications' => 'Signal Processing, Quantum Mechanics'], + ['id' => 6, 'name' => 'Central Limit Theorem', 'mathematician' => 'Pierre-Simon Laplace', 'field' => 'Probability', 'year_discovered' => 1810, 'difficulty_level' => 6, 'is_proven' => true, 'statement' => 'Sum of random variables approaches normal distribution', 'applications' => 'Statistics, Data Science'], + ['id' => 7, 'name' => 'Riemann Hypothesis', 'mathematician' => 'Bernhard Riemann', 'field' => 'Number Theory', 'year_discovered' => 1859, 'difficulty_level' => 10, 'is_proven' => false, 'statement' => 'All non-trivial zeros of ζ(s) have real part 1/2', 'applications' => 'Prime Number Distribution'], + ['id' => 8, 'name' => 'Cauchy\'s Integral Theorem', 'mathematician' => 'Augustin-Louis Cauchy', 'field' => 'Complex Analysis', 'year_discovered' => 1825, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Integral of holomorphic function over closed curve is zero', 'applications' => 'Engineering, Physics'], + ['id' => 9, 'name' => 'Banach Fixed-Point Theorem', 'mathematician' => 'Stefan Banach', 'field' => 'Functional Analysis', 'year_discovered' => 1922, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Contraction mappings have unique fixed points', 'applications' => 'Differential Equations'], + ['id' => 10, 'name' => 'Nash Equilibrium Theorem', 'mathematician' => 'John Nash', 'field' => 'Game Theory', 'year_discovered' => 1950, 'difficulty_level' => 6, 'is_proven' => true, 'statement' => 'Every finite game has a mixed strategy equilibrium', 'applications' => 'Economics, Political Science'], + ['id' => 11, 'name' => 'Stokes\' Theorem', 'mathematician' => 'George Stokes', 'field' => 'Vector Calculus', 'year_discovered' => 1854, 'difficulty_level' => 8, 'is_proven' => true, 'statement' => 'Relates surface integral to line integral', 'applications' => 'Fluid Dynamics, Electromagnetism'], + ['id' => 12, 'name' => 'Brouwer Fixed-Point Theorem', 'mathematician' => 'L.E.J. Brouwer', 'field' => 'Topology', 'year_discovered' => 1911, 'difficulty_level' => 8, 'is_proven' => true, 'statement' => 'Continuous function from ball to itself has fixed point', 'applications' => 'Economics, Differential Equations'], + ['id' => 13, 'name' => 'Four Color Theorem', 'mathematician' => 'Kenneth Appel & Wolfgang Haken', 'field' => 'Graph Theory', 'year_discovered' => 1976, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Any planar map needs at most 4 colors', 'applications' => 'Cartography, Computer Science'], + ['id' => 14, 'name' => 'Poincaré Conjecture', 'mathematician' => 'Grigori Perelman', 'field' => 'Topology', 'year_discovered' => 2003, 'difficulty_level' => 10, 'is_proven' => true, 'statement' => 'Every simply connected 3-manifold is homeomorphic to 3-sphere', 'applications' => 'Cosmology, Shape of Universe'], + ['id' => 15, 'name' => 'Law of Large Numbers', 'mathematician' => 'Jakob Bernoulli', 'field' => 'Probability', 'year_discovered' => 1713, 'difficulty_level' => 5, 'is_proven' => true, 'statement' => 'Sample average converges to expected value', 'applications' => 'Statistics, Machine Learning'], + ['id' => 16, 'name' => 'Fundamental Theorem of Algebra', 'mathematician' => 'Carl Friedrich Gauss', 'field' => 'Algebra', 'year_discovered' => 1799, 'difficulty_level' => 6, 'is_proven' => true, 'statement' => 'Every polynomial has at least one complex root', 'applications' => 'Control Theory, Signal Processing'], + ['id' => 17, 'name' => 'Green\'s Theorem', 'mathematician' => 'George Green', 'field' => 'Vector Calculus', 'year_discovered' => 1828, 'difficulty_level' => 6, 'is_proven' => true, 'statement' => 'Relates line integral to double integral', 'applications' => 'Fluid Mechanics, Electrostatics'], + ['id' => 18, 'name' => 'Spectral Theorem', 'mathematician' => 'David Hilbert', 'field' => 'Linear Algebra', 'year_discovered' => 1906, 'difficulty_level' => 8, 'is_proven' => true, 'statement' => 'Normal operators have orthonormal eigenbasis', 'applications' => 'Quantum Mechanics, Principal Component Analysis'], + ['id' => 19, 'name' => 'Bolzano-Weierstrass Theorem', 'mathematician' => 'Bernard Bolzano & Karl Weierstrass', 'field' => 'Analysis', 'year_discovered' => 1817, 'difficulty_level' => 6, 'is_proven' => true, 'statement' => 'Bounded sequence has convergent subsequence', 'applications' => 'Real Analysis, Optimization'], + ['id' => 20, 'name' => 'Hahn-Banach Theorem', 'mathematician' => 'Hans Hahn & Stefan Banach', 'field' => 'Functional Analysis', 'year_discovered' => 1927, 'difficulty_level' => 9, 'is_proven' => true, 'statement' => 'Bounded linear functionals can be extended', 'applications' => 'Optimization, Economics'], + ['id' => 21, 'name' => 'Intermediate Value Theorem', 'mathematician' => 'Bernard Bolzano', 'field' => 'Analysis', 'year_discovered' => 1817, 'difficulty_level' => 4, 'is_proven' => true, 'statement' => 'Continuous function takes all intermediate values', 'applications' => 'Root Finding, Numerical Analysis'], + ['id' => 22, 'name' => 'Cayley-Hamilton Theorem', 'mathematician' => 'Arthur Cayley & William Hamilton', 'field' => 'Linear Algebra', 'year_discovered' => 1858, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Every matrix satisfies its characteristic equation', 'applications' => 'Control Systems, Matrix Functions'], + ['id' => 23, 'name' => 'Liouville\'s Theorem', 'mathematician' => 'Joseph Liouville', 'field' => 'Complex Analysis', 'year_discovered' => 1844, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Bounded entire function is constant', 'applications' => 'Complex Analysis, Physics'], + ['id' => 24, 'name' => 'Prime Number Theorem', 'mathematician' => 'Jacques Hadamard & Charles de la Vallée-Poussin', 'field' => 'Number Theory', 'year_discovered' => 1896, 'difficulty_level' => 9, 'is_proven' => true, 'statement' => 'π(x) ~ x/ln(x) as x → ∞', 'applications' => 'Cryptography, Number Distribution'], + ['id' => 25, 'name' => 'Bayes\' Theorem', 'mathematician' => 'Thomas Bayes', 'field' => 'Probability', 'year_discovered' => 1763, 'difficulty_level' => 5, 'is_proven' => true, 'statement' => 'P(A|B) = P(B|A)P(A)/P(B)', 'applications' => 'Machine Learning, Medical Diagnosis'], + ['id' => 26, 'name' => 'Divergence Theorem', 'mathematician' => 'Carl Friedrich Gauss', 'field' => 'Vector Calculus', 'year_discovered' => 1813, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Flux through surface equals volume integral of divergence', 'applications' => 'Electromagnetism, Fluid Dynamics'], + ['id' => 27, 'name' => 'Noether\'s Theorem', 'mathematician' => 'Emmy Noether', 'field' => 'Mathematical Physics', 'year_discovered' => 1915, 'difficulty_level' => 9, 'is_proven' => true, 'statement' => 'Every symmetry has a corresponding conservation law', 'applications' => 'Particle Physics, General Relativity'], + ['id' => 28, 'name' => 'Cantor\'s Theorem', 'mathematician' => 'Georg Cantor', 'field' => 'Set Theory', 'year_discovered' => 1891, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Power set is strictly larger than original set', 'applications' => 'Foundations of Mathematics'], + ['id' => 29, 'name' => 'Mean Value Theorem', 'mathematician' => 'Augustin-Louis Cauchy', 'field' => 'Analysis', 'year_discovered' => 1823, 'difficulty_level' => 5, 'is_proven' => true, 'statement' => 'f\'(c) = (f(b) - f(a))/(b - a) for some c', 'applications' => 'Optimization, Error Analysis'], + ['id' => 30, 'name' => 'Isoperimetric Inequality', 'mathematician' => 'Jakob Steiner', 'field' => 'Geometry', 'year_discovered' => 1838, 'difficulty_level' => 8, 'is_proven' => true, 'statement' => 'Circle encloses maximum area for given perimeter', 'applications' => 'Optimization, Calculus of Variations'], + ['id' => 31, 'name' => 'Collatz Conjecture', 'mathematician' => 'Lothar Collatz', 'field' => 'Number Theory', 'year_discovered' => 1937, 'difficulty_level' => 10, 'is_proven' => false, 'statement' => 'All sequences reach 1 via 3n+1 or n/2', 'applications' => 'Dynamical Systems'], + ['id' => 32, 'name' => 'P vs NP Problem', 'mathematician' => 'Stephen Cook', 'field' => 'Complexity Theory', 'year_discovered' => 1971, 'difficulty_level' => 10, 'is_proven' => false, 'statement' => 'Can every verified solution be found quickly?', 'applications' => 'Computer Science, Cryptography'], + ['id' => 33, 'name' => 'Riesz Representation Theorem', 'mathematician' => 'Frigyes Riesz', 'field' => 'Functional Analysis', 'year_discovered' => 1907, 'difficulty_level' => 8, 'is_proven' => true, 'statement' => 'Continuous linear functionals representable as integrals', 'applications' => 'Quantum Mechanics, PDEs'], + ['id' => 34, 'name' => 'Rolle\'s Theorem', 'mathematician' => 'Michel Rolle', 'field' => 'Analysis', 'year_discovered' => 1691, 'difficulty_level' => 4, 'is_proven' => true, 'statement' => 'f\'(c) = 0 for some c if f(a) = f(b)', 'applications' => 'Calculus, Root Finding'], + ['id' => 35, 'name' => 'Goldbach Conjecture', 'mathematician' => 'Christian Goldbach', 'field' => 'Number Theory', 'year_discovered' => 1742, 'difficulty_level' => 10, 'is_proven' => false, 'statement' => 'Every even integer > 2 is sum of two primes', 'applications' => 'Number Theory Research'], + ]; + } +} + +``` + +> this is just a demo that's why we choose array models, your case will use real laravel modes capabilities, but all the docs is the same regardless of the data source + +### First Working Datatable + + +Include the `App\Livewire\Concerns\WithPagination` trait in your Livewire component: + +```php + +use App\Models\Theorem; +use App\Livewire\Concerns\WithSorting; +use App\Livewire\Concerns\WithPagination; + +class Theorems extends Component +{ +{+ use WithPagination;+} + + public function render() + { +{+ $theorems = $this->baseQuery()->paginate($this->perPage);+} + + return view('livewire.theorems', [ + 'theorems' => $theorems, + ]); + } + +{+ protected function baseQuery(): Builder + { + return Theorem::query(); + }+} +} +``` + +- we're using our custom `WithPagination` trait, for extra feature (`$perPage` property and it's updated hook 🙂), We're using `Livewire\WithPagination` underneath +- we also extract our `baseQuery` function wich we're going to use it heavly in comming sections. + +for the view side is simple as: + +```blade + + + + + #ID + + + Theorem + + + Mathematician + + + Field + + + Year + + + Difficulty + + + Status + + + + + + @forelse($paginator as $theorem) + + + {{ $theorem->id }} + + +
+
+ {{ $theorem->name }} +
+
+ {{ $theorem->statement }} +
+
+
+ + +
+ {{ $theorem->mathematician }} +
+
+ + + @php + + $fieldColors = [ + 'Number Theory' => 'purple', + 'Analysis' => 'blue', + 'Geometry' => 'green', + 'Algebra' => 'red', + 'Topology' => 'orange', + 'Probability' => 'pink', + 'Complex Analysis' => 'cyan', + 'Functional Analysis' => 'indigo', + 'Vector Calculus' => 'teal', + 'Game Theory' => 'violet', + 'Graph Theory' => 'lime', + 'Logic' => 'amber', + 'Linear Algebra' => 'rose', + 'Set Theory' => 'fuchsia', + 'Mathematical Physics' => 'sky', + 'Complexity Theory' => 'emerald', + ]; + $color = $fieldColors[$theorem->field] ?? 'neutral'; + @endphp + + {{ $theorem->field }} + + + + +
+ {{ $theorem->year_discovered < 0 ? abs($theorem->year_discovered) . ' BC' : $theorem->year_discovered }} +
+
+ + +
+ @for($i = 1; $i <= min($theorem->difficulty_level, 10); $i++) + + + + @endfor +
+
+ Level {{ $theorem->difficulty_level }} +
+
+ + + @if($theorem->is_proven) + + Proven + + @else + + Conjecture + + @endif + +
+ @endforelse +
+
+``` + +### Add Sorting + +Enable column sorting with visual indicators and flexible behavior. + +#### Step 1: Add the Trait + +Include the `App\Livewire\Concerns\WithSorting` trait in your Livewire component: + +```php +use App\Models\Theorem; +use App\Livewire\Concerns\WithSorting; +use App\Livewire\Concerns\WithPagination; + +class Theorems extends Component +{ + {+ + use WithSorting;+} use WithPagination; - use WithSorting; public function render() { - $users = User::query() - ->when(filled($this->sortBy), function ($query) { + $theorems = $this->baseQuery() +{+ ->when(filled($this->sortBy), function ($query) { return $this->applySorting($query); - }) + })+} ->paginate($this->perPage); - return view('livewire.users-table', [ - 'users' => $users, + return view('livewire.theorems', [ + 'theorems' => $theorems, ]); } - - protected function sortableColumns(): array + protected function baseQuery(): Builder { - return ['name', 'email', 'created_at']; + return Theorem::query(); } } ``` +`$this->sortBy` and `$this->applySorting()` are coming from that `WithSorting` trait. + +> Note, if you want to restrict sorting on the backend side you can use the `sortableColumns()` method on the component class + #### Step 2: Update the View Mark columns as sortable and pass the current sort state: @@ -450,8 +737,8 @@ namespace App\Livewire; use App\Models\User; use Livewire\Component; -use Src\Components\Livewire\Concerns\WithPagination; -use Src\Components\Livewire\Concerns\WithSearch; +use App\Livewire\Concerns\WithPagination; +use App\Livewire\Concerns\WithSearch; class UsersTable extends Component { @@ -460,14 +747,14 @@ class UsersTable extends Component public function render() { - $users = User::query() + $theorems = User::query() ->when(filled($this->searchQuery), function ($query) { return $this->applySearch($query); }) ->paginate($this->perPage); - return view('livewire.users-table', [ - 'users' => $users, + return view('livewire.theorems', [ + 'theorems' => $theorems, ]); } @@ -494,12 +781,12 @@ class UsersTable extends Component Use the table's `top` slot to add a search field: ```blade - +
@@ -574,7 +861,7 @@ Provide helpful feedback when no results are found. -

No users found

+

No theorems found

Try adjusting your search or filters.

@@ -588,7 +875,7 @@ Provide helpful feedback when no results are found. ```blade - @forelse ($users as $user) + @forelse ($theorems as $user) {{ $user->name }} {{ $user->email }} @@ -601,7 +888,7 @@ Provide helpful feedback when no results are found. -

No users found

+

No theorems found

Try adjusting your search or create a new user.

@@ -627,7 +914,7 @@ namespace App\Livewire; use App\Models\User; use Livewire\Component; -use Src\Components\Livewire\Concerns\WithSelection; +use App\Livewire\Concerns\WithSelection; class UsersTable extends Component { @@ -635,15 +922,15 @@ class UsersTable extends Component public function render() { - $users = User::query()->paginate($this->perPage); + $theorems = User::query()->paginate($this->perPage); // Store visible IDs for "select all" functionality - $this->visibleIds = $users->pluck('id') + $this->visibleIds = $theorems->pluck('id') ->map(fn ($id) => (string) $id) ->toArray(); - return view('livewire.users-table', [ - 'users' => $users, + return view('livewire.theorems', [ + 'theorems' => $theorems, ]); } } @@ -662,7 +949,7 @@ Enable the "check all" header and add checkboxes to rows: - @foreach ($users as $user) + @foreach ($theorems as $user) selectedIds)) { - $users = $users->whereIn('id', $this->selectedIds); + $theorems = $theorems->whereIn('id', $this->selectedIds); } - return $this->csv($users->get()); + return $this->csv($theorems->get()); } } ``` @@ -731,7 +1018,7 @@ class UsersTable extends Component Use the `top` slot to add bulk action controls: ```blade - +
@@ -758,7 +1045,7 @@ Use the `top` slot to add bulk action controls: icon="trash" variant="danger" wire:click="deleteSelected" - wire:confirm="Are you sure you want to delete the selected users?" + wire:confirm="Are you sure you want to delete the selected theorems?" > Delete Selected @@ -791,7 +1078,7 @@ public function deleteSelected() // Validate that IDs are valid $this->validate([ 'selectedIds' => 'required|array|min:1', - 'selectedIds.*' => 'integer|exists:users,id', + 'selectedIds.*' => 'integer|exists:theorems,id', ]); // Optional: Add authorization @@ -806,7 +1093,7 @@ public function deleteSelected() // Notify user $this->dispatch('notify', [ - 'message' => "{$deleted} users deleted successfully", + 'message' => "{$deleted} theorems deleted successfully", 'type' => 'success' ]); } @@ -821,7 +1108,7 @@ Override methods in the `CanExportCsv` trait to customize the export: ```php protected function getCsvFilename(): string { - return 'users_export_' . now()->format('Y-m-d_His') . '.csv'; + return 'theorems_export_' . now()->format('Y-m-d_His') . '.csv'; } protected function getExportableColumns(): array @@ -833,7 +1120,7 @@ protected function getExportableColumns(): array ### Add Column Visibility -Let users show/hide columns dynamically. +Let theorems show/hide columns dynamically. @blade @@ -908,10 +1195,10 @@ Let users show/hide columns dynamically. ```blade
- +
@@ -955,7 +1242,7 @@ Let users show/hide columns dynamically. - @foreach ($users as $user) + @foreach ($theorems as $user) {{ $user->name }} @@ -995,7 +1282,7 @@ Add the `draggable` attribute to your table: ```blade @@ -1039,12 +1326,12 @@ class UsersTable extends Component public function render() { - $users = User::query() + $theorems = User::query() ->orderBy('sort_order') ->paginate($this->perPage); - return view('livewire.users-table', [ - 'users' => $users, + return view('livewire.theorems', [ + 'theorems' => $theorems, ]); } } @@ -1073,7 +1360,7 @@ Implement advanced filtering with multiple criteria. ```php when(filled($this->filters), function ($query) { return $this->applyFilters($query); }) ->paginate($this->perPage); - return view('livewire.users-table', [ - 'users' => $users, + return view('livewire.theorems', [ + 'theorems' => $theorems, ]); } } @@ -1138,7 +1425,7 @@ class UsersTable extends Component #### Step 3: Add Filter UI ```blade - +
@@ -1349,7 +1636,7 @@ Customize the table's appearance with utility classes and variants. ```blade paginate($this->perPage); ``` @@ -1805,7 +2092,7 @@ public function deleteSelected() { $this->validate([ 'selectedIds' => 'required|array|min:1', - 'selectedIds.*' => 'integer|exists:users,id', + 'selectedIds.*' => 'integer|exists:theorems,id', ]); // Safe to proceed @@ -1908,14 +2195,14 @@ Ensure you're setting `visibleIds` in your render method: ```php public function render() { - $users = User::query()->paginate($this->perPage); + $theorems = User::query()->paginate($this->perPage); // This is crucial for checkbox sync - $this->visibleIds = $users->pluck('id') + $this->visibleIds = $theorems->pluck('id') ->map(fn ($id) => (string) $id) ->toArray(); - return view('livewire.users-table', ['users' => $users]); + return view('livewire.theorems', ['theorems' => $theorems]); } ``` From a5cba62301856a22f2f7b219325306cfed2695fc Mon Sep 17 00:00:00 2001 From: charrafimed Date: Thu, 22 Jan 2026 17:22:10 +0100 Subject: [PATCH 14/40] wip --- components/table/usage.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/components/table/usage.md b/components/table/usage.md index c48d79a..b5a5efc 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -156,6 +156,29 @@ In your table header view, mark sortable columns and pass the current sort state - `sortable` marks the column as sortable. - `currentSortBy` and `currentSortDir` are reactive Livewire properties tracking the current sort state. +### Sorting variants +There are two sorting variants: + +- `default`: shows sorting icons on hover and cycles sorting on click. +- `dropdown`: opens a menu where the user explicitly chooses the sorting direction. + +```blade + +} + +{+ variant="dropdown"+} +> + name + +``` +You can see these variants in action on our interactive [Math Theorems demo](/demos/datatables). +Sorting by year uses the dropdown variant, while mathematician and difficulty columns use the default variant. + + ## Stickiness Make columns or headers stick to the viewport while scrolling. From 6df8119ddb17350c553a4a54bc5660b0f1475fa5 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Thu, 22 Jan 2026 18:02:38 +0100 Subject: [PATCH 15/40] wip --- components/table/usage.md | 286 +++++++++++++------------------------- 1 file changed, 93 insertions(+), 193 deletions(-) diff --git a/components/table/usage.md b/components/table/usage.md index b5a5efc..97c95c2 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -74,44 +74,6 @@ Let's start with a simple static table without any dynamic features. ``` -## Pagination - -Add pagination to your table by passing a Laravel paginator to table component. - -### Creating the Livewire Component - -First, create a Livewire component that uses the `App\Livewire\Concerns\WithPagination` trait: - -```php - use \App\Livewire\Concerns\WithPagination; - // livewire class side - $theorems = User::query()->paginate(); - // or (if you using length aware paginator with full variant) - // ->paginate($this->perPage); -``` - -### The View - -```blade -
- - - -
-``` - -### Customizing Items Per Page - -The `App\Livewire\Concerns\WithPagination` trait includes a `$perPage` property that defaults to 15. You can override it in the trait, don't forget the code is yours tweack it as you want. - -### More About Pagination ? -@blade - -@endblade ## Sorting This component includes everything you need to add sorting to your tables. To enable sorting, first add the `App\Livewire\Concerns\WithSorting` trait to your Livewire component like this: @@ -140,6 +102,8 @@ class Theorems extends Component } } ``` +`$this->sortBy` and `$this->applySorting()` are coming from that `WithSorting` trait. + In your table header view, mark sortable columns and pass the current sort state like this: ```blade @@ -156,6 +120,9 @@ In your table header view, mark sortable columns and pass the current sort state - `sortable` marks the column as sortable. - `currentSortBy` and `currentSortDir` are reactive Livewire properties tracking the current sort state. +> Note, if you want to restrict sorting on the backend side you can use the `sortableColumns()` method on the component class + + ### Sorting variants There are two sorting variants: @@ -178,6 +145,45 @@ There are two sorting variants: You can see these variants in action on our interactive [Math Theorems demo](/demos/datatables). Sorting by year uses the dropdown variant, while mathematician and difficulty columns use the default variant. +## Pagination + +Add pagination to your table by passing a Laravel paginator to table component. + +First, create a Livewire component that uses the `App\Livewire\Concerns\WithPagination` trait: + +```php + use \App\Livewire\Concerns\WithPagination; + // livewire class side + $users = User::query() +{+ ->paginate();+} + // or (if you using length aware paginator with full variant) +{+ ->paginate($this->perPage);+} +``` +and pass the users collection to the `paginator` prop on the view: + +```blade +
+ + + +
+``` + + +### More About Pagination ? +@blade + +@endblade + +## Loading Logic +due the nature of datatables are usually data heavy. + ## Stickiness @@ -306,12 +312,15 @@ Make the first column stick when scrolling horizontally: > **Note:** When using sticky header, columns, always apply a background color to prevent content overlap during scrolling. -## Feature Guides -in this guide I am going to walk throught using this data-table component with bunch of others to create a stunning data tables, I am going to list list of math theorems and thier founding year and the mathematiciens behind them..., +## Implementation Guide + +### Overview + +This guide walks you through using the data table component alongside other utilities to create a polished, feature-rich data table. We'll display a list of mathematical theorems, including their discovery year and the mathematicians behind them. -### Setup Theorem Model +### Setup Theorems Data -at first we need a `Theorem` model with (id, name, mathematician, field, year_discovered, difficulty_level, is_proven, statement and applications), we are using sushi for this demo wich our `App\Models\Theorem` looks like this: +First, create a`Theorem` model with fields: (id, name, mathematician, field, year_discovered, difficulty_level, is_proven, statement and applications), For this demo, we use the Sushi package with an in-memory array model. Our `App\Models\Theorem` looks like this: ```php this is just a demo that's why we choose array models, your case will use real laravel modes capabilities, but all the docs is the same regardless of the data source +> This is a demo using array-based models for simplicity. Your real application will use standard Laravel Eloquent models, but the integration remains the same regardless of data source. -### First Working Datatable +### Table with Pagination -Include the `App\Livewire\Concerns\WithPagination` trait in your Livewire component: +Add the `App\Livewire\Concerns\WithPagination` trait to your Livewire component to enable pagination: ```php @@ -410,52 +419,34 @@ class Theorems extends Component } ``` -- we're using our custom `WithPagination` trait, for extra feature (`$perPage` property and it's updated hook 🙂), We're using `Livewire\WithPagination` underneath -- we also extract our `baseQuery` function wich we're going to use it heavly in comming sections. +- We use a custom `WithPagination` trait that extends Livewire's native pagination with `$perPage` property and reactive updates. +- The `baseQuery` method encapsulates the core query builder, making it reusable for sorting, filtering, and other operations in later steps. -for the view side is simple as: +The view integrates pagination, displays theorem details with badges and icons: ```blade - - - - #ID + + + + ID Theorem - + Mathematician Field - + Year - + Difficulty @@ -548,6 +539,8 @@ for the view side is simple as: @endif + @empty + @endforelse @@ -591,149 +584,56 @@ class Theorems extends Component } ``` -`$this->sortBy` and `$this->applySorting()` are coming from that `WithSorting` trait. - -> Note, if you want to restrict sorting on the backend side you can use the `sortableColumns()` method on the component class #### Step 2: Update the View -Mark columns as sortable and pass the current sort state: +Let's make `mathematicien`, `year` and `dificulty` sortable, while making sort by year special by using the `dropdown` sorting variant for sorting: ```blade - + - + #ID + + + Theorem + + - Name + Mathematician - - + Field + + - Email + Year - - - Created At + Difficulty + + + Status ``` -#### Sorting Variants - -The table supports two sorting variants: `default` (click to toggle) and `dropdown` (menu-based). - -**Default Variant:** - -@blade - - - - - - Product - - - Price - - - - - - Widget A - $29.99 - - - - -@endblade - -```blade - - Name - -``` - -**Dropdown Variant:** - -@blade - - - - - - Product - - - Price - - - - - - Widget A - $29.99 - - - - -@endblade - -```blade - - Name - -``` - -The dropdown variant provides "Sort Ascending", "Sort Descending", and "Clear Sort" options. - #### How It Works The `WithSorting` trait provides: From 7324f3e3ca86ee5be6c1bbd1d740167513749701 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Thu, 22 Jan 2026 18:06:46 +0100 Subject: [PATCH 16/40] wip --- components/table/usage.md | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/components/table/usage.md b/components/table/usage.md index 97c95c2..a227f6c 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -585,9 +585,9 @@ class Theorems extends Component ``` -#### Step 2: Update the View +#### Step 2: Updates on the View -Let's make `mathematicien`, `year` and `dificulty` sortable, while making sort by year special by using the `dropdown` sorting variant for sorting: +Let's make `mathematicien`, `year` and `Difficulty` sortable, while making sort by year special by using the `dropdown` sorting variant for sorting: ```blade @@ -599,10 +599,10 @@ Let's make `mathematicien`, `year` and `dificulty` sortable, while making sort b Theorem Mathematician @@ -610,19 +610,19 @@ Let's make `mathematicien`, `year` and `dificulty` sortable, while making sort b Field Year Difficulty @@ -634,17 +634,6 @@ Let's make `mathematicien`, `year` and `dificulty` sortable, while making sort b ``` -#### How It Works - -The `WithSorting` trait provides: - -- `sortByColumn($column, $dir = null)` - Handles sort toggling -- `clearSorting()` - Resets sorting state -- `applySorting($query)` - Applies sort to the query -- `sortableColumns()` - Whitelist of sortable columns - -Clicking a sortable header cycles through: **asc → desc → no sort**. - ### Add Search Enable real-time search across your table data. From c2523e2ea47587ac7c1b1f095560f33003d72d79 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Thu, 22 Jan 2026 18:39:47 +0100 Subject: [PATCH 17/40] wip --- components/table/usage.md | 52 ++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/components/table/usage.md b/components/table/usage.md index a227f6c..1b418c0 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -181,6 +181,51 @@ and pass the users collection to the `paginator` prop on the view: /> @endblade +## Search +This component includes everything you need to add search to your tables. To enable search, first add the `App\Livewire\Concerns\WithSorting` trait to your Livewire component like this: + +```php +{~ +use App\Models\User; +use App\Livewire\Concerns\WithSearch; +use App\Livewire\Concerns\WithPagination; +class Theorems extends Component +{ ~} + {+ + use WithSearch;+} + + public function render() + { + $users = User::query() +{+ ->when(filled($this->searchQuery), function ($query) { + return $this->applySearch($query); + })+} + ->paginate(); + + return view('livewire.users', [ + 'users' => $users, + ]); + } + +{+ protected function applySearch($query) + { + return $query->where('name', 'like', '%'.$this->searchQuery.'%'); + }+} +} +``` + +then binding to an input that you can put in the top of the table + +```blade + +} +/> +``` + +see the search on the guide below for real layout. + ## Loading Logic due the nature of datatables are usually data heavy. @@ -592,12 +637,7 @@ Let's make `mathematicien`, `year` and `Difficulty` sortable, while making sort ```blade - - #ID - - - Theorem - + Date: Thu, 22 Jan 2026 20:44:00 +0100 Subject: [PATCH 18/40] wip --- components/table/usage.md | 163 +++++++++++++++----------------------- 1 file changed, 63 insertions(+), 100 deletions(-) diff --git a/components/table/usage.md b/components/table/usage.md index 1b418c0..0107997 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -214,6 +214,8 @@ class Theorems extends Component } ``` +the `applySearch` is up to you to implement... + then binding to an input that you can put in the top of the table ```blade @@ -635,6 +637,15 @@ class Theorems extends Component Let's make `mathematicien`, `year` and `Difficulty` sortable, while making sort by year special by using the `dropdown` sorting variant for sorting: ```blade + + @@ -676,7 +687,7 @@ Let's make `mathematicien`, `year` and `Difficulty` sortable, while making sort ### Add Search -Enable real-time search across your table data. +Enable real-time search across your table data, #### Step 1: Add the Trait @@ -700,9 +711,12 @@ class UsersTable extends Component public function render() { $theorems = User::query() - ->when(filled($this->searchQuery), function ($query) { - return $this->applySearch($query); + ->when(filled($this->sortBy), function ($query) { + return $this->applySorting($query); }) +{+ ->when(filled($this->searchQuery), function ($query) { + return $this->applySearch($query); + })+} ->paginate($this->perPage); return view('livewire.theorems', [ @@ -710,90 +724,55 @@ class UsersTable extends Component ]); } - protected function applySearch($query) +{+ protected function applySearch($query) { - $search = str_replace( - ['\\', '%', '_'], - ['\\\\', '\\%', '\\_'], - $this->searchQuery - ); - - return $query->where(function($q) use ($search) { - $q->where('name', 'like', "%{$search}%") - ->orWhere('email', 'like', "%{$search}%"); - }); - } + return $query->where('name', 'like', '%'.$this->searchQuery.'%') + ->orWhere('mathematician', 'like', '%'.$this->searchQuery.'%') + ->orWhere('field', 'like', '%'.$this->searchQuery.'%') + ->orWhere('statemen', 'like', '%'.$this->searchQuery.'%'); + }+} } ``` -> **Security Note:** Always escape LIKE wildcards (`%`, `_`) and wrap OR conditions in a closure to prevent SQL logic bugs. #### Step 2: Add the Search Input -Use the table's `top` slot to add a search field: +first let's wrap our table and all of filters logic into the `table.container` blade component, wich is responsible for manaing padding between pagination table and filters in a good way, ```blade - - +{+ + {{-- SEARCH INPUT --}}
+ placeholder="search..." leftIcon="magnifying-glass" - wire:model.live.debounce.300ms="searchQuery" + wire:model.live="searchQuery" />
-
- - -
-``` - -#### Optimizing Search Performance - -**Use Debouncing:** - -```blade -wire:model.live.debounce.300ms="searchQuery" -``` - -This reduces server requests by waiting 300ms after the user stops typing. - -**Use Model Scopes:** - -Instead of putting search logic in the component, create a reusable scope: - -```php -// App/Models/User.php -public function scopeSearch($query, string $search) -{ - $search = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $search); - - return $query->where(function($q) use ($search) { - $q->where('name', 'like', "%{$search}%") - ->orWhere('email', 'like', "%{$search}%"); - }); -} - -// In your component -protected function applySearch($query) -{ - return $query->search($this->searchQuery); -} -``` - -**Use Full-Text Search for Large Datasets:** - -```php -protected function applySearch($query) -{ - return $query->whereFullText(['name', 'email'], $this->searchQuery); -} +
++} + + + +{+ @@ -833,12 +812,11 @@ Provide helpful feedback when no results are found. {{ $user->email }}
@empty - +{+ -

No theorems found

@@ -846,7 +824,7 @@ Provide helpful feedback when no results are found.

-
+
+} @endforelse
``` @@ -868,18 +846,18 @@ use App\Models\User; use Livewire\Component; use App\Livewire\Concerns\WithSelection; -class UsersTable extends Component +class Theorems extends Component { - use WithSelection; +{+ use WithSelection;+} public function render() { - $theorems = User::query()->paginate($this->perPage); + $theorems = $this->baseQuery()->...(); // Store visible IDs for "select all" functionality - $this->visibleIds = $theorems->pluck('id') +{+ $this->visibleIds = $theorems->pluck('id') ->map(fn ($id) => (string) $id) - ->toArray(); + ->toArray();+} return view('livewire.theorems', [ 'theorems' => $theorems, @@ -893,18 +871,17 @@ class UsersTable extends Component Enable the "check all" header and add checkboxes to rows: ```blade - - - Name - Email - - + + + @foreach ($theorems as $user) {{ $user->name }} {{ $user->email }} @@ -913,21 +890,7 @@ Enable the "check all" header and add checkboxes to rows: ``` -#### How It Works - -The checkbox system uses Alpine.js for smooth client-side interactions: - -- Clicking the header checkbox toggles all visible rows -- Individual checkboxes update the selection state -- The header shows an indeterminate state when some (but not all) rows are selected -- Selected IDs are stored in the `$selectedIds` array (as strings) - -**The header checkbox has three states:** -- **Checked** - All visible rows selected -- **Indeterminate** - Some rows selected -- **Unchecked** - No rows selected - -### Bulk Actions (CSV Export Example) +### Bulk Actions (Delete and CSV Export Example) Perform actions on multiple selected rows. From ae02594bdd4130de10cac0371a7db59ebe3544f4 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Thu, 22 Jan 2026 20:53:46 +0100 Subject: [PATCH 19/40] wip --- components/table/usage.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/components/table/usage.md b/components/table/usage.md index 0107997..4c12503 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -227,6 +227,10 @@ then binding to an input that you can put in the top of the table ``` see the search on the guide below for real layout. +## Selection + +the component cames with all logic you need to add select rows and select all functionality for handling bulk actions... + ## Loading Logic due the nature of datatables are usually data heavy. @@ -374,7 +378,7 @@ First, create a`Theorem` model with fields: (id, name, mathematician, field, yea declare(strict_types=1); -namespace Src\Components\Models; +namespace App\Models; use Illuminate\Database\Eloquent\Model; use Sushi\Sushi; @@ -778,12 +782,7 @@ Provide helpful feedback when no results are found. use our [empty state compone - - Name - Email - - @@ -807,10 +806,7 @@ Provide helpful feedback when no results are found. use our [empty state compone ```blade @forelse ($theorems as $user) - - {{ $user->name }} - {{ $user->email }} - + @empty {+ @@ -844,10 +840,16 @@ namespace App\Livewire; use App\Models\User; use Livewire\Component; + +use App\Livewire\Concerns\CanExportCsv; use App\Livewire\Concerns\WithSelection; class Theorems extends Component { +{~ use CanExportCsv; + use WithPagination; + use WithSearch; + use WithSorting;~} {+ use WithSelection;+} public function render() @@ -892,7 +894,7 @@ Enable the "check all" header and add checkboxes to rows: ### Bulk Actions (Delete and CSV Export Example) -Perform actions on multiple selected rows. +let add a checkbox button that shows only when there is selected rows. #### Step 1: Add the CSV Export Trait @@ -917,7 +919,7 @@ class UsersTable extends Component #[Renderless] public function exportSelected() { - $theorems = User::query(); + $theorems = $this->baseQuery(); if (filled($this->selectedIds)) { $theorems = $theorems->whereIn('id', $this->selectedIds); From 984f733f77b3d95a9b989998a10875ac9fc28018 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Thu, 22 Jan 2026 21:19:48 +0100 Subject: [PATCH 20/40] wip --- components/table/usage.md | 158 ++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 85 deletions(-) diff --git a/components/table/usage.md b/components/table/usage.md index 4c12503..04850ca 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -846,8 +846,7 @@ use App\Livewire\Concerns\WithSelection; class Theorems extends Component { -{~ use CanExportCsv; - use WithPagination; +{~ use WithPagination; use WithSearch; use WithSorting;~} {+ use WithSelection;+} @@ -894,7 +893,7 @@ Enable the "check all" header and add checkboxes to rows: ### Bulk Actions (Delete and CSV Export Example) -let add a checkbox button that shows only when there is selected rows. +let add a checkbox button that shows only when there is selected rows. going to add delete and export to csv the selected rows #### Step 1: Add the CSV Export Trait @@ -905,28 +904,40 @@ Include the `CanExportCsv` trait: namespace App\Livewire; -use App\Models\User; -use Livewire\Component; -use Livewire\Attributes\Renderless; -use App\Livewire\Concerns\WithSelection; -use App\Livewire\Concerns\CanExportCsv; - -class UsersTable extends Component +class Theorems extends Component { - use WithSelection; - use CanExportCsv; +{~ + use WithPagination; + use WithSearch; + use WithSorting; + use WithSelection;~} +{+ use CanExportCsv;+} - #[Renderless] +{+ #[Renderless] public function exportSelected() { $theorems = $this->baseQuery(); - if (filled($this->selectedIds)) { $theorems = $theorems->whereIn('id', $this->selectedIds); } - + // + apply filters like search and sorting if you want + // then convert them into csv... return $this->csv($theorems->get()); - } + }+} + + // other parts... + +{+ public function deleteSelected() + { + + // ⚠️ don't forget validation & authorizations + + // Gate::authorize('delete-theorem', User::class); + this->baseQuery()->whereIn('id',$this->selectedIds)->delete() + + // you may clear selection after deletes + $this->deselectAll(); + }+} } ``` @@ -935,104 +946,81 @@ class UsersTable extends Component Use the `top` slot to add bulk action controls: ```blade - - - -
- + +
+{+ {{-- BULK ACTIONS --}} +
+ + - Bulk Actions + bulk action + + - Export Selected to CSV + export selected csv - Delete Selected + delete selected
++} - + {{-- SEARCH INPUT --}}
- - - - -``` - -#### Implementing Delete Action - -Add a delete method with proper validation: - -```php -public function deleteSelected() -{ - // Validate that IDs are valid - $this->validate([ - 'selectedIds' => 'required|array|min:1', - 'selectedIds.*' => 'integer|exists:theorems,id', - ]); - - // Optional: Add authorization - // Gate::authorize('delete-multiple', User::class); - - $deleted = User::query() - ->whereIn('id', $this->selectedIds) - ->delete(); - - // Clear selection - $this->selectedIds = []; - - // Notify user - $this->dispatch('notify', [ - 'message' => "{$deleted} theorems deleted successfully", - 'type' => 'success' - ]); -} -``` - -> **Security:** Always validate `selectedIds` to prevent client-side manipulation. Never trust data from the frontend! - -#### Customizing CSV Export - -Override methods in the `CanExportCsv` trait to customize the export: - -```php -protected function getCsvFilename(): string -{ - return 'theorems_export_' . now()->format('Y-m-d_His') . '.csv'; -} - -protected function getExportableColumns(): array -{ - // Limit which columns are exported - return ['id', 'name', 'email', 'created_at']; -} +
+ + + +
``` ### Add Column Visibility From 4dc9155f5eff483d286a34c1fd71b2c35fd2d035 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Thu, 22 Jan 2026 21:44:40 +0100 Subject: [PATCH 21/40] doing some really progress on data table docs*... --- components/table/usage.md | 198 ++++++++++---------------------------- 1 file changed, 52 insertions(+), 146 deletions(-) diff --git a/components/table/usage.md b/components/table/usage.md index 04850ca..fb78f3b 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -8,7 +8,7 @@ name: 'table' The `table` component provides a powerful, composable system for building feature-rich data tables. Built with Livewire and Alpine.js, it offers pagination, sorting, searching, selection, bulk actions, column visibility controls, drag-and-drop reordering, and more all with excellent accessibility and a clean, modern design. -Our approach uses **composable traits** on the backend and **slot-based components** on the frontend, giving you complete control over your table's structure and behavior while maintaining clean, reusable code. +Our approach uses **composable traits** on the backend and **blade components** on the frontend, giving you complete control over your table's structure and behavior while maintaining clean, reusable code. ## Installation @@ -229,7 +229,51 @@ then binding to an input that you can put in the top of the table see the search on the guide below for real layout. ## Selection -the component cames with all logic you need to add select rows and select all functionality for handling bulk actions... +the component cames with all logic you need to add select rows and select all functionality for handling bulk actions..., To enable selection, first add the `App\Livewire\Concerns\WithSelection` trait to your Livewire component like this: + +```php +{~ +use App\Models\User; +use App\Livewire\Concerns\WithSelection; +class Theorems extends Component +{ ~} + {+ + use WithSelection;+} + + public function render() + { + $users = User::query()->....->paginate(); + + // this is crucial, it tell us about the visible rows on the page +{+ $this->visibleIds = $theorems->pluck('id') + ->map(fn ($id) => (string) $id) + ->toArray();+} + + return view('livewire.users', [ + 'users' => $users, + ]); + } +} +``` + +then on the view add the `:checkboxId` to each `table.row` blade component to show show the checkbox on the start of the row, then to add check all functionality add `checkAll` to the `table.columns` blade component + +```blade + + + + + + +... +``` + +see the search on the guide below for real layout. ## Loading Logic @@ -879,13 +923,12 @@ Enable the "check all" header and add checkboxes to rows: - @foreach ($theorems as $user) + @foreach ($theorems as $theorem) - {{ $user->name }} - {{ $user->email }} + @endforeach @@ -1029,7 +1072,7 @@ Let theorems show/hide columns dynamically. @blade -
+
@@ -1258,144 +1301,7 @@ Add event dispatch to the sortable container: ### Add Filters -Implement advanced filtering with multiple criteria. - -#### Step 1: Create a Filter Trait - -```php -filters as $field => $value) { - if (filled($value)) { - $query->where($field, $value); - } - } - - return $query; - } - - public function resetFilters() - { - $this->filters = []; - $this->resetPage(); - } - - public function updatedFilters() - { - $this->resetPage(); - } -} -``` - -#### Step 2: Use in Component - -```php -when(filled($this->filters), function ($query) { - return $this->applyFilters($query); - }) - ->paginate($this->perPage); - - return view('livewire.theorems', [ - 'theorems' => $theorems, - ]); - } -} -``` - -#### Step 3: Add Filter UI - -```blade - - - -
- - - - - - - - - - - - - - - Clear Filters - -
- - -
- -
-
- - -
-``` - -#### Advanced Filter Example with Date Ranges - -```php -public function applyFilters($query) -{ - if (filled($this->filters['role'])) { - $query->where('role', $this->filters['role']); - } - - if (filled($this->filters['date_from'])) { - $query->where('created_at', '>=', $this->filters['date_from']); - } - - if (filled($this->filters['date_to'])) { - $query->where('created_at', '<=', $this->filters['date_to']); - } - - return $query; -} -``` +Implement advanced filtering with multiple criteria going to add docs for this after adding this capability to the core of the component... ### Playing with Table Design From 97e1f83cd9b5e0f25e2fa791a3626a5b671f2a30 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Fri, 23 Jan 2026 11:52:00 +0100 Subject: [PATCH 22/40] wip --- components/table/usage.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/components/table/usage.md b/components/table/usage.md index fb78f3b..c690afb 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -273,8 +273,21 @@ then on the view add the `:checkboxId` to each `table.row` blade component to sh >... ``` -see the search on the guide below for real layout. +then if you want to operations on the selected Ids you can do it like so: + +```php + public function deleteSelected() + { + User::query()->whereIn('id',$this->selectedIds)->delete() + } + + public function archiveSelected() + { + User::query()->whereIn('id',$this->selectedIds)->each->archive() + } + // ... +``` ## Loading Logic due the nature of datatables are usually data heavy. From 60f832d94ed4d6f7d4f8604956ca1fe8fa4d5de0 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Fri, 23 Jan 2026 12:06:48 +0100 Subject: [PATCH 23/40] add clumn visibilty docs --- components/table/usage.md | 348 +++++++++++++++++++++++++++----------- 1 file changed, 253 insertions(+), 95 deletions(-) diff --git a/components/table/usage.md b/components/table/usage.md index c690afb..8eced5b 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -1081,71 +1081,140 @@ Use the `top` slot to add bulk action controls: ### Add Column Visibility -Let theorems show/hide columns dynamically. +Let's make status and difficulty hiddeable columns on our theorems table well this is how. @blade -
+
- + - Columns - + class="rounded-box ml-2 outline dark:outline-white/20 outline-neutral-900/10 dark:ring-white/15 ring-neutral-900/15 [[data-open]>&]:bg-white/5 [[data-open]>&]:ring-2 shadow-sm" + /> - - - - Name - - - Email - - - Role - - - Status - + + + hidden columns + + + + difficulty + + + status +
- - - Name + + #ID - - Email + + Therem - - Role + + Mathematician - + + Field + + + Year + + + Difficulty + + Status - - - Alice Johnson + + 1 + + + Fundamental Theorem of Calculus + + + Isaac Newton & Gottfried Leibniz + + + + Analysis + + + + 1666 + + + 7 + + + + Proven + + + + + + 2 + + + Fermat's Last Theorem + + + Andrew Wiles + + + + Number Theory + - - alice@example.com + + 1995 - - Admin + + 10 - - Active + + + Proven + @@ -1155,70 +1224,159 @@ Let theorems show/hide columns dynamically. @endblade ```blade -
- - - -
- + +
+ + + + + +{+ {{-- HIDDEN COLUMNS --}} + - Columns - + class="rounded-box ml-2 outline dark:outline-white/20 outline-neutral-900/10 dark:ring-white/15 ring-neutral-900/15 [[data-open]>&]:bg-white/5 [[data-open]>&]:ring-2 shadow-sm" + /> - - - Name - - - Email - - - Created At - + + + hidden columns + + + + difficulty + + + status + - + +}
- - - - - - Name - - - Email - - - Created At - - - - - - @foreach ($theorems as $user) - - - {{ $user->name }} - - - {{ $user->email }} - - - {{ $user->created_at->format('M d, Y') }} - - - @endforeach - - -
+ + + + + + #ID + + + Theorem + + + Mathematician + + + Field + + + Year + + + Difficulty + + + Status + + + + + + @forelse($paginator as $theorem) + + + {{ $theorem->id }} + + + + + + +
+ {{ $theorem->mathematician }} +
+
+ + + + + {{ $theorem->field }} + + + + +
+ {{ $theorem->year_discovered < 0 ? abs($theorem->year_discovered) . ' BC' : $theorem->year_discovered }} +
+
+ + + +
+ Level {{ $theorem->difficulty_level }} +
+
+ + + @if($theorem->is_proven) + + Proven + + @else + + Conjecture + + @endif + +
+ + @endforelse +
+
+ ``` #### Persisting Column Preferences @@ -1227,7 +1385,7 @@ Use Alpine's `$persist` plugin to save user preferences across sessions: ```blade x-data="{ - visibleCols: $persist(['name', 'email']).as('table-visible-columns') + hiddenCols: $persist(['status', 'difficulty']).as('table-hidden-columns') }" ``` From 1c154277d652db983676e61a4add6d871cc4771d Mon Sep 17 00:00:00 2001 From: charrafimed Date: Fri, 23 Jan 2026 12:31:28 +0100 Subject: [PATCH 24/40] wip --- components/table/usage.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/components/table/usage.md b/components/table/usage.md index 8eced5b..5fd8ad2 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -290,8 +290,30 @@ then if you want to operations on the selected Ids you can do it like so: ``` ## Loading Logic -due the nature of datatables are usually data heavy. +due the nature of datatables are usually data heavy. this component cames with pre-pretty way to handle that +to enable loading indicators just add `wire:loading` to the table component, and for convenience we have make easy to enable loading on sorting,searching, pagination by passing them coma separated to the `loadOn` prop to the table component + +```blade + +``` +if you want to add loading on sorting, search, pagination requests you can use the `loadOn` prop +```blade + +``` + +to add other target you can use `wire:target` as you would with in regurar usage. +```blade + +``` ## Stickiness From 908aad7d6832fbb56407b44dc02fad3ab9c4baa8 Mon Sep 17 00:00:00 2001 From: charrafimed Date: Fri, 23 Jan 2026 14:23:53 +0100 Subject: [PATCH 25/40] complete current data table implementation docs --- components/table/usage.md | 1368 +++++++++++++------------------------ 1 file changed, 462 insertions(+), 906 deletions(-) diff --git a/components/table/usage.md b/components/table/usage.md index 5fd8ad2..10e39d7 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -6,9 +6,9 @@ name: 'table' ## Introduction -The `table` component provides a powerful, composable system for building feature-rich data tables. Built with Livewire and Alpine.js, it offers pagination, sorting, searching, selection, bulk actions, column visibility controls, drag-and-drop reordering, and more all with excellent accessibility and a clean, modern design. +The `table` component provides a powerful, composable system for building feature-rich data tables. Built with Livewire and Alpine.js, it offers pagination, sorting, searching, selection, bulk actions, column visibility controls, and more all with excellent accessibility and a clean, modern design. -Our approach uses **composable traits** on the backend and **blade components** on the frontend, giving you complete control over your table's structure and behavior while maintaining clean, reusable code. +Our approach uses **composable traits** on the backend and **Blade components** on the frontend, giving you complete control over your table's structure and behavior while maintaining clean, reusable code. ## Installation @@ -74,17 +74,20 @@ Let's start with a simple static table without any dynamic features. ``` +--- + +## Sorting -## Sorting -This component includes everything you need to add sorting to your tables. To enable sorting, first add the `App\Livewire\Concerns\WithSorting` trait to your Livewire component like this: +Add column sorting with visual indicators and flexible behavior. To enable sorting, first add the `App\Livewire\Concerns\WithSorting` trait to your Livewire component: ```php {~ use App\Models\User; use App\Livewire\Concerns\WithSorting; use App\Livewire\Concerns\WithPagination; -class Theorems extends Component -{ ~} + +class UsersTable extends Component +{~} {+ use WithSorting;+} @@ -96,15 +99,16 @@ class Theorems extends Component })+} ->paginate(); - return view('livewire.users', [ + return view('livewire.users-table', [ 'users' => $users, ]); } } ``` -`$this->sortBy` and `$this->applySorting()` are coming from that `WithSorting` trait. -In your table header view, mark sortable columns and pass the current sort state like this: +The `$this->sortBy` and `$this->applySorting()` methods come from the `WithSorting` trait. + +In your table header view, mark sortable columns and pass the current sort state: ```blade - name + Name ``` -- `column` should match your database column name. -- `sortable` marks the column as sortable. -- `currentSortBy` and `currentSortDir` are reactive Livewire properties tracking the current sort state. -> Note, if you want to restrict sorting on the backend side you can use the `sortableColumns()` method on the component class +**Key attributes:** +- `column` — Database column name +- `sortable` — Marks the column as sortable +- `currentSortBy` and `currentSortDir` — Reactive Livewire properties tracking sort state +> **Note:** If you want to restrict sorting on the backend, you can use the `sortableColumns()` method on the component class. + +### Sorting Variants -### Sorting variants There are two sorting variants: -- `default`: shows sorting icons on hover and cycles sorting on click. -- `dropdown`: opens a menu where the user explicitly chooses the sorting direction. +- **`default`** — Shows sorting icons on hover and cycles sorting on click +- **`dropdown`** — Opens a menu where the user explicitly chooses the sorting direction ```blade +} +{+ variant="default" +} {+ variant="dropdown"+} > - name + Name ``` -You can see these variants in action on our interactive [Math Theorems demo](/demos/datatables). -Sorting by year uses the dropdown variant, while mathematician and difficulty columns use the default variant. + +You can see these variants in action on our interactive [Math Theorems demo](/demos/datatables). Sorting by year uses the dropdown variant, while mathematician and difficulty columns use the default variant. + +--- ## Pagination -Add pagination to your table by passing a Laravel paginator to table component. +Add pagination to your table by passing a Laravel paginator to the table component. First, create a Livewire component that uses the `App\Livewire\Concerns\WithPagination` trait: ```php - use \App\Livewire\Concerns\WithPagination; - // livewire class side - $users = User::query() -{+ ->paginate();+} - // or (if you using length aware paginator with full variant) -{+ ->paginate($this->perPage);+} +use App\Livewire\Concerns\WithPagination; + +class UsersTable extends Component +{ + use WithPagination; + + public function render() + { + $users = User::query() +{+ ->paginate();+} + // or (if using length-aware paginator with full variant) +{+ ->paginate($this->perPage);+} + + return view('livewire.users-table', [ + 'users' => $users, + ]); + } +} ``` -and pass the users collection to the `paginator` prop on the view: + +Then pass the paginator to the `paginator` prop in the view: ```blade -
- - - -
+ + + ``` +### More About Pagination -### More About Pagination ? @blade @endblade -## Search -This component includes everything you need to add search to your tables. To enable search, first add the `App\Livewire\Concerns\WithSorting` trait to your Livewire component like this: +--- + +## Search + +Enable real-time search across your table data. To enable search, first add the `App\Livewire\Concerns\WithSearch` trait to your Livewire component: ```php {~ use App\Models\User; use App\Livewire\Concerns\WithSearch; use App\Livewire\Concerns\WithPagination; -class Theorems extends Component -{ ~} + +class UsersTable extends Component +{~} {+ use WithSearch;+} @@ -202,119 +225,137 @@ class Theorems extends Component })+} ->paginate(); - return view('livewire.users', [ + return view('livewire.users-table', [ 'users' => $users, ]); } -{+ protected function applySearch($query) +{+ protected function applySearch($query) { return $query->where('name', 'like', '%'.$this->searchQuery.'%'); }+} } ``` -the `applySearch` is up to you to implement... +The `applySearch()` method is where you define your search logic. -then binding to an input that you can put in the top of the table +Then bind an input to the search query: ```blade - +} +{+ wire:model.live="searchQuery" +} /> ``` -see the search on the guide below for real layout. +See the search implementation in the complete guide below for a real-world layout. + +--- + ## Selection -the component cames with all logic you need to add select rows and select all functionality for handling bulk actions..., To enable selection, first add the `App\Livewire\Concerns\WithSelection` trait to your Livewire component like this: +The component comes with all the logic you need to add row selection and "select all" functionality for handling bulk actions. To enable selection, first add the `App\Livewire\Concerns\WithSelection` trait to your Livewire component: ```php {~ use App\Models\User; use App\Livewire\Concerns\WithSelection; -class Theorems extends Component -{ ~} + +class UsersTable extends Component +{~} {+ use WithSelection;+} public function render() { - $users = User::query()->....->paginate(); + $users = User::query()->paginate(); - // this is crucial, it tell us about the visible rows on the page -{+ $this->visibleIds = $theorems->pluck('id') + // This is crucial - it tells us about the visible rows on the page +{+ $this->visibleIds = $users->pluck('id') ->map(fn ($id) => (string) $id) ->toArray();+} - return view('livewire.users', [ + return view('livewire.users-table', [ 'users' => $users, ]); } } ``` -then on the view add the `:checkboxId` to each `table.row` blade component to show show the checkbox on the start of the row, then to add check all functionality add `checkAll` to the `table.columns` blade component +Then in the view, add `:checkboxId` to each `table.row` component to show the checkbox at the start of the row. To add "check all" functionality, add `withCheckAll` to the `table.columns` component: ```blade - + - + ... +> + + ``` -then if you want to operations on the selected Ids you can do it like so: +Now you can perform operations on the selected IDs like this: ```php - public function deleteSelected() - { - User::query()->whereIn('id',$this->selectedIds)->delete() - } - - public function archiveSelected() - { - User::query()->whereIn('id',$this->selectedIds)->each->archive() - } +public function deleteSelected() +{ + User::query()->whereIn('id', $this->selectedIds)->delete(); +} - // ... +public function archiveSelected() +{ + User::query()->whereIn('id', $this->selectedIds)->each->archive(); +} ``` -## Loading Logic -due the nature of datatables are usually data heavy. this component cames with pre-pretty way to handle that +--- + +## Loading States + +Due to the nature of datatables being usually data-heavy, this component comes with a clean way to handle loading states. -to enable loading indicators just add `wire:loading` to the table component, and for convenience we have make easy to enable loading on sorting,searching, pagination by passing them coma separated to the `loadOn` prop to the table component +To enable loading indicators, just add `wire:loading` to the table component: ```blade + + ``` -if you want to add loading on sorting, search, pagination requests you can use the `loadOn` prop + +For convenience, we've made it easy to enable loading on sorting, searching, and pagination by passing them comma-separated to the `loadOn` prop: + ```blade + + ``` -to add other target you can use `wire:target` as you would with in regurar usage. +To add other targets, you can use `wire:target` as you would in regular usage: + ```blade + + ``` +--- + ## Stickiness Make columns or headers stick to the viewport while scrolling. @@ -439,18 +480,21 @@ Make the first column stick when scrolling horizontally: ``` -> **Note:** When using sticky header, columns, always apply a background color to prevent content overlap during scrolling. +> **Note:** When using sticky headers or columns, always apply a background color to prevent content overlap during scrolling. +--- ## Implementation Guide ### Overview -This guide walks you through using the data table component alongside other utilities to create a polished, feature-rich data table. We'll display a list of mathematical theorems, including their discovery year and the mathematicians behind them. +This guide walks you through building a polished, feature-rich data table using the table component alongside other utilities. We'll display a list of mathematical theorems, including their discovery year and the mathematicians behind them. ### Setup Theorems Data -First, create a`Theorem` model with fields: (id, name, mathematician, field, year_discovered, difficulty_level, is_proven, statement and applications), For this demo, we use the Sushi package with an in-memory array model. Our `App\Models\Theorem` looks like this: +First, create a `Theorem` model with fields: `id`, `name`, `mathematician`, `field`, `year_discovered`, `difficulty_level`, `is_proven`, `statement`, and `applications`. + +For this demo, we use the Sushi package with an in-memory array model. Our `App\Models\Theorem` looks like this: ```php 1, 'name' => 'Pythagorean Theorem', 'mathematician' => 'Pythagoras', 'field' => 'Geometry', 'year_discovered' => -500, 'difficulty_level' => 2, 'is_proven' => true, 'statement' => 'In a right triangle, a² + b² = c²', 'applications' => 'Architecture, Navigation'], ['id' => 2, 'name' => 'Fundamental Theorem of Calculus', 'mathematician' => 'Isaac Newton & Gottfried Leibniz', 'field' => 'Analysis', 'year_discovered' => 1666, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Links differentiation and integration', 'applications' => 'Physics, Engineering'], - ['id' => 3, 'name' => 'Fermat\'s Last Theorem', 'mathematician' => 'Andrew Wiles', 'field' => 'Number Theory', 'year_discovered' => 1995, 'difficulty_level' => 10, 'is_proven' => true, 'statement' => 'No three positive integers satisfy aⁿ + bⁿ = cⁿ for n > 2', 'applications' => 'Pure Mathematics'], - ['id' => 4, 'name' => 'Gödel\'s Incompleteness Theorems', 'mathematician' => 'Kurt Gödel', 'field' => 'Logic', 'year_discovered' => 1931, 'difficulty_level' => 9, 'is_proven' => true, 'statement' => 'Any consistent formal system has unprovable truths', 'applications' => 'Computer Science, Philosophy'], - ['id' => 5, 'name' => 'Euler\'s Identity', 'mathematician' => 'Leonhard Euler', 'field' => 'Complex Analysis', 'year_discovered' => 1748, 'difficulty_level' => 6, 'is_proven' => true, 'statement' => 'e^(iπ) + 1 = 0', 'applications' => 'Signal Processing, Quantum Mechanics'], - ['id' => 6, 'name' => 'Central Limit Theorem', 'mathematician' => 'Pierre-Simon Laplace', 'field' => 'Probability', 'year_discovered' => 1810, 'difficulty_level' => 6, 'is_proven' => true, 'statement' => 'Sum of random variables approaches normal distribution', 'applications' => 'Statistics, Data Science'], - ['id' => 7, 'name' => 'Riemann Hypothesis', 'mathematician' => 'Bernhard Riemann', 'field' => 'Number Theory', 'year_discovered' => 1859, 'difficulty_level' => 10, 'is_proven' => false, 'statement' => 'All non-trivial zeros of ζ(s) have real part 1/2', 'applications' => 'Prime Number Distribution'], - ['id' => 8, 'name' => 'Cauchy\'s Integral Theorem', 'mathematician' => 'Augustin-Louis Cauchy', 'field' => 'Complex Analysis', 'year_discovered' => 1825, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Integral of holomorphic function over closed curve is zero', 'applications' => 'Engineering, Physics'], - ['id' => 9, 'name' => 'Banach Fixed-Point Theorem', 'mathematician' => 'Stefan Banach', 'field' => 'Functional Analysis', 'year_discovered' => 1922, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Contraction mappings have unique fixed points', 'applications' => 'Differential Equations'], - ['id' => 10, 'name' => 'Nash Equilibrium Theorem', 'mathematician' => 'John Nash', 'field' => 'Game Theory', 'year_discovered' => 1950, 'difficulty_level' => 6, 'is_proven' => true, 'statement' => 'Every finite game has a mixed strategy equilibrium', 'applications' => 'Economics, Political Science'], - ['id' => 11, 'name' => 'Stokes\' Theorem', 'mathematician' => 'George Stokes', 'field' => 'Vector Calculus', 'year_discovered' => 1854, 'difficulty_level' => 8, 'is_proven' => true, 'statement' => 'Relates surface integral to line integral', 'applications' => 'Fluid Dynamics, Electromagnetism'], - ['id' => 12, 'name' => 'Brouwer Fixed-Point Theorem', 'mathematician' => 'L.E.J. Brouwer', 'field' => 'Topology', 'year_discovered' => 1911, 'difficulty_level' => 8, 'is_proven' => true, 'statement' => 'Continuous function from ball to itself has fixed point', 'applications' => 'Economics, Differential Equations'], - ['id' => 13, 'name' => 'Four Color Theorem', 'mathematician' => 'Kenneth Appel & Wolfgang Haken', 'field' => 'Graph Theory', 'year_discovered' => 1976, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Any planar map needs at most 4 colors', 'applications' => 'Cartography, Computer Science'], - ['id' => 14, 'name' => 'Poincaré Conjecture', 'mathematician' => 'Grigori Perelman', 'field' => 'Topology', 'year_discovered' => 2003, 'difficulty_level' => 10, 'is_proven' => true, 'statement' => 'Every simply connected 3-manifold is homeomorphic to 3-sphere', 'applications' => 'Cosmology, Shape of Universe'], - ['id' => 15, 'name' => 'Law of Large Numbers', 'mathematician' => 'Jakob Bernoulli', 'field' => 'Probability', 'year_discovered' => 1713, 'difficulty_level' => 5, 'is_proven' => true, 'statement' => 'Sample average converges to expected value', 'applications' => 'Statistics, Machine Learning'], - ['id' => 16, 'name' => 'Fundamental Theorem of Algebra', 'mathematician' => 'Carl Friedrich Gauss', 'field' => 'Algebra', 'year_discovered' => 1799, 'difficulty_level' => 6, 'is_proven' => true, 'statement' => 'Every polynomial has at least one complex root', 'applications' => 'Control Theory, Signal Processing'], - ['id' => 17, 'name' => 'Green\'s Theorem', 'mathematician' => 'George Green', 'field' => 'Vector Calculus', 'year_discovered' => 1828, 'difficulty_level' => 6, 'is_proven' => true, 'statement' => 'Relates line integral to double integral', 'applications' => 'Fluid Mechanics, Electrostatics'], - ['id' => 18, 'name' => 'Spectral Theorem', 'mathematician' => 'David Hilbert', 'field' => 'Linear Algebra', 'year_discovered' => 1906, 'difficulty_level' => 8, 'is_proven' => true, 'statement' => 'Normal operators have orthonormal eigenbasis', 'applications' => 'Quantum Mechanics, Principal Component Analysis'], - ['id' => 19, 'name' => 'Bolzano-Weierstrass Theorem', 'mathematician' => 'Bernard Bolzano & Karl Weierstrass', 'field' => 'Analysis', 'year_discovered' => 1817, 'difficulty_level' => 6, 'is_proven' => true, 'statement' => 'Bounded sequence has convergent subsequence', 'applications' => 'Real Analysis, Optimization'], - ['id' => 20, 'name' => 'Hahn-Banach Theorem', 'mathematician' => 'Hans Hahn & Stefan Banach', 'field' => 'Functional Analysis', 'year_discovered' => 1927, 'difficulty_level' => 9, 'is_proven' => true, 'statement' => 'Bounded linear functionals can be extended', 'applications' => 'Optimization, Economics'], - ['id' => 21, 'name' => 'Intermediate Value Theorem', 'mathematician' => 'Bernard Bolzano', 'field' => 'Analysis', 'year_discovered' => 1817, 'difficulty_level' => 4, 'is_proven' => true, 'statement' => 'Continuous function takes all intermediate values', 'applications' => 'Root Finding, Numerical Analysis'], - ['id' => 22, 'name' => 'Cayley-Hamilton Theorem', 'mathematician' => 'Arthur Cayley & William Hamilton', 'field' => 'Linear Algebra', 'year_discovered' => 1858, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Every matrix satisfies its characteristic equation', 'applications' => 'Control Systems, Matrix Functions'], - ['id' => 23, 'name' => 'Liouville\'s Theorem', 'mathematician' => 'Joseph Liouville', 'field' => 'Complex Analysis', 'year_discovered' => 1844, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Bounded entire function is constant', 'applications' => 'Complex Analysis, Physics'], - ['id' => 24, 'name' => 'Prime Number Theorem', 'mathematician' => 'Jacques Hadamard & Charles de la Vallée-Poussin', 'field' => 'Number Theory', 'year_discovered' => 1896, 'difficulty_level' => 9, 'is_proven' => true, 'statement' => 'π(x) ~ x/ln(x) as x → ∞', 'applications' => 'Cryptography, Number Distribution'], - ['id' => 25, 'name' => 'Bayes\' Theorem', 'mathematician' => 'Thomas Bayes', 'field' => 'Probability', 'year_discovered' => 1763, 'difficulty_level' => 5, 'is_proven' => true, 'statement' => 'P(A|B) = P(B|A)P(A)/P(B)', 'applications' => 'Machine Learning, Medical Diagnosis'], - ['id' => 26, 'name' => 'Divergence Theorem', 'mathematician' => 'Carl Friedrich Gauss', 'field' => 'Vector Calculus', 'year_discovered' => 1813, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Flux through surface equals volume integral of divergence', 'applications' => 'Electromagnetism, Fluid Dynamics'], - ['id' => 27, 'name' => 'Noether\'s Theorem', 'mathematician' => 'Emmy Noether', 'field' => 'Mathematical Physics', 'year_discovered' => 1915, 'difficulty_level' => 9, 'is_proven' => true, 'statement' => 'Every symmetry has a corresponding conservation law', 'applications' => 'Particle Physics, General Relativity'], - ['id' => 28, 'name' => 'Cantor\'s Theorem', 'mathematician' => 'Georg Cantor', 'field' => 'Set Theory', 'year_discovered' => 1891, 'difficulty_level' => 7, 'is_proven' => true, 'statement' => 'Power set is strictly larger than original set', 'applications' => 'Foundations of Mathematics'], - ['id' => 29, 'name' => 'Mean Value Theorem', 'mathematician' => 'Augustin-Louis Cauchy', 'field' => 'Analysis', 'year_discovered' => 1823, 'difficulty_level' => 5, 'is_proven' => true, 'statement' => 'f\'(c) = (f(b) - f(a))/(b - a) for some c', 'applications' => 'Optimization, Error Analysis'], - ['id' => 30, 'name' => 'Isoperimetric Inequality', 'mathematician' => 'Jakob Steiner', 'field' => 'Geometry', 'year_discovered' => 1838, 'difficulty_level' => 8, 'is_proven' => true, 'statement' => 'Circle encloses maximum area for given perimeter', 'applications' => 'Optimization, Calculus of Variations'], - ['id' => 31, 'name' => 'Collatz Conjecture', 'mathematician' => 'Lothar Collatz', 'field' => 'Number Theory', 'year_discovered' => 1937, 'difficulty_level' => 10, 'is_proven' => false, 'statement' => 'All sequences reach 1 via 3n+1 or n/2', 'applications' => 'Dynamical Systems'], - ['id' => 32, 'name' => 'P vs NP Problem', 'mathematician' => 'Stephen Cook', 'field' => 'Complexity Theory', 'year_discovered' => 1971, 'difficulty_level' => 10, 'is_proven' => false, 'statement' => 'Can every verified solution be found quickly?', 'applications' => 'Computer Science, Cryptography'], - ['id' => 33, 'name' => 'Riesz Representation Theorem', 'mathematician' => 'Frigyes Riesz', 'field' => 'Functional Analysis', 'year_discovered' => 1907, 'difficulty_level' => 8, 'is_proven' => true, 'statement' => 'Continuous linear functionals representable as integrals', 'applications' => 'Quantum Mechanics, PDEs'], - ['id' => 34, 'name' => 'Rolle\'s Theorem', 'mathematician' => 'Michel Rolle', 'field' => 'Analysis', 'year_discovered' => 1691, 'difficulty_level' => 4, 'is_proven' => true, 'statement' => 'f\'(c) = 0 for some c if f(a) = f(b)', 'applications' => 'Calculus, Root Finding'], - ['id' => 35, 'name' => 'Goldbach Conjecture', 'mathematician' => 'Christian Goldbach', 'field' => 'Number Theory', 'year_discovered' => 1742, 'difficulty_level' => 10, 'is_proven' => false, 'statement' => 'Every even integer > 2 is sum of two primes', 'applications' => 'Number Theory Research'], + // ... (rest of theorems) ]; } } - ``` -> This is a demo using array-based models for simplicity. Your real application will use standard Laravel Eloquent models, but the integration remains the same regardless of data source. +> **Note:** This demo uses array-based models for simplicity. Your real application will use standard Laravel Eloquent models, but the integration remains the same regardless of data source. ### Table with Pagination - Add the `App\Livewire\Concerns\WithPagination` trait to your Livewire component to enable pagination: ```php - use App\Models\Theorem; -use App\Livewire\Concerns\WithSorting; use App\Livewire\Concerns\WithPagination; +use Illuminate\Database\Eloquent\Builder; class Theorems extends Component { -{+ use WithPagination;+} +{+ use WithPagination;+} public function render() { -{+ $theorems = $this->baseQuery()->paginate($this->perPage);+} +{+ $theorems = $this->baseQuery()->paginate($this->perPage);+} return view('livewire.theorems', [ 'theorems' => $theorems, ]); } -{+ protected function baseQuery(): Builder +{+ protected function baseQuery(): Builder { return Theorem::query(); }+} } ``` -- We use a custom `WithPagination` trait that extends Livewire's native pagination with `$perPage` property and reactive updates. -- The `baseQuery` method encapsulates the core query builder, making it reusable for sorting, filtering, and other operations in later steps. +**Key points:** +- We use a custom `WithPagination` trait that extends Livewire's native pagination with a `$perPage` property and reactive updates +- The `baseQuery()` method encapsulates the core query builder, making it reusable for sorting, filtering, and other operations in later steps -The view integrates pagination, displays theorem details with badges and icons: +The view integrates pagination and displays theorem details with badges and icons: ```blade - - ID - - - Theorem - - - Mathematician - - - Field - - - Year - - - Difficulty - - - Status - + ID + Theorem + Mathematician + Field + Year + Difficulty + Status - @forelse($paginator as $theorem) - + @forelse($theorems as $theorem) + {{ $theorem->id }} - + +
{{ $theorem->name }} @@ -612,24 +607,11 @@ The view integrates pagination, displays theorem details with badges and icons: @php - $fieldColors = [ 'Number Theory' => 'purple', 'Analysis' => 'blue', 'Geometry' => 'green', - 'Algebra' => 'red', - 'Topology' => 'orange', - 'Probability' => 'pink', - 'Complex Analysis' => 'cyan', - 'Functional Analysis' => 'indigo', - 'Vector Calculus' => 'teal', - 'Game Theory' => 'violet', - 'Graph Theory' => 'lime', - 'Logic' => 'amber', - 'Linear Algebra' => 'rose', - 'Set Theory' => 'fuchsia', - 'Mathematical Physics' => 'sky', - 'Complexity Theory' => 'emerald', + // ... more field colors ]; $color = $fieldColors[$theorem->field] ?? 'neutral'; @endphp @@ -670,7 +652,7 @@ The view integrates pagination, displays theorem details with badges and icons: @empty - + @endforelse @@ -691,8 +673,7 @@ use App\Livewire\Concerns\WithPagination; class Theorems extends Component { - {+ - use WithSorting;+} +{+ use WithSorting;+} use WithPagination; public function render() @@ -707,6 +688,7 @@ class Theorems extends Component 'theorems' => $theorems, ]); } + protected function baseQuery(): Builder { return Theorem::query(); @@ -714,63 +696,64 @@ class Theorems extends Component } ``` +#### Step 2: Update the View -#### Step 2: Updates on the View - -Let's make `mathematicien`, `year` and `Difficulty` sortable, while making sort by year special by using the `dropdown` sorting variant for sorting: +Let's make `mathematician`, `year`, and `difficulty` sortable, while making sort by year special by using the `dropdown` sorting variant: ```blade - - - - - - Mathematician - - - Field - - - Year - - - Difficulty - - - Status - - - + + + + + Mathematician + + + + Field + + + + Year + + + + Difficulty + + + + Status + + + + + + ``` ### Add Search -Enable real-time search across your table data, +Enable real-time search across your table data. #### Step 1: Add the Trait @@ -781,23 +764,25 @@ Include the `WithSearch` trait: namespace App\Livewire; -use App\Models\User; +use App\Models\Theorem; use Livewire\Component; use App\Livewire\Concerns\WithPagination; +use App\Livewire\Concerns\WithSorting; use App\Livewire\Concerns\WithSearch; -class UsersTable extends Component +class Theorems extends Component { use WithPagination; - use WithSearch; + use WithSorting; +{+ use WithSearch;+} public function render() { - $theorems = User::query() + $theorems = $this->baseQuery() ->when(filled($this->sortBy), function ($query) { return $this->applySorting($query); }) -{+ ->when(filled($this->searchQuery), function ($query) { +{+ ->when(filled($this->searchQuery), function ($query) { return $this->applySearch($query); })+} ->paginate($this->perPage); @@ -812,50 +797,47 @@ class UsersTable extends Component return $query->where('name', 'like', '%'.$this->searchQuery.'%') ->orWhere('mathematician', 'like', '%'.$this->searchQuery.'%') ->orWhere('field', 'like', '%'.$this->searchQuery.'%') - ->orWhere('statemen', 'like', '%'.$this->searchQuery.'%'); + ->orWhere('statement', 'like', '%'.$this->searchQuery.'%'); }+} + + protected function baseQuery(): Builder + { + return Theorem::query(); + } } ``` - #### Step 2: Add the Search Input -first let's wrap our table and all of filters logic into the `table.container` blade component, wich is responsible for manaing padding between pagination table and filters in a good way, +First, let's wrap our table and all filter logic into the `table.container` Blade component, which is responsible for managing padding between pagination, table, and filters in a clean way: ```blade -{+ +{++} +{+
{{-- SEARCH INPUT --}}
- placeholder="search..." + class="[&_input]:bg-transparent" + placeholder="Search..." leftIcon="magnifying-glass" wire:model.live="searchQuery" />
-
-+} +
+} + - + -{++} ``` ### Handle Empty States -Provide helpful feedback when no results are found. use our [empty state component](/docs/components/empty) +Provide helpful feedback when no results are found using our [empty state component](/docs/components/empty): @blade @@ -882,6 +864,8 @@ Provide helpful feedback when no results are found. use our [empty state compone @endblade + + ```blade @forelse ($theorems as $user) @@ -917,25 +901,26 @@ Include the `WithSelection` trait: namespace App\Livewire; -use App\Models\User; +use App\Models\Theorem; use Livewire\Component; - -use App\Livewire\Concerns\CanExportCsv; +use App\Livewire\Concerns\WithPagination; +use App\Livewire\Concerns\WithSorting; +use App\Livewire\Concerns\WithSearch; use App\Livewire\Concerns\WithSelection; class Theorems extends Component { {~ use WithPagination; - use WithSearch; - use WithSorting;~} -{+ use WithSelection;+} + use WithSorting; + use WithSearch;~} +{+ use WithSelection;+} public function render() { $theorems = $this->baseQuery()->...(); // Store visible IDs for "select all" functionality -{+ $this->visibleIds = $theorems->pluck('id') +{+ $this->visibleIds = $theorems->pluck('id') ->map(fn ($id) => (string) $id) ->toArray();+} @@ -952,18 +937,20 @@ Enable the "check all" header and add checkboxes to rows: ```blade + - + + @foreach ($theorems as $theorem) - + @endforeach @@ -971,7 +958,7 @@ Enable the "check all" header and add checkboxes to rows: ### Bulk Actions (Delete and CSV Export Example) -let add a checkbox button that shows only when there is selected rows. going to add delete and export to csv the selected rows +Let's add bulk action buttons that only show when there are selected rows. We'll implement delete and CSV export for the selected rows. #### Step 1: Add the CSV Export Trait @@ -982,52 +969,56 @@ Include the `CanExportCsv` trait: namespace App\Livewire; +use App\Livewire\Concerns\CanExportCsv; +use Livewire\Attributes\Renderless; + class Theorems extends Component { -{~ - use WithPagination; - use WithSearch; +{~ use WithPagination; use WithSorting; + use WithSearch; use WithSelection;~} -{+ use CanExportCsv;+} +{+ use CanExportCsv;+} {+ #[Renderless] public function exportSelected() { $theorems = $this->baseQuery(); + if (filled($this->selectedIds)) { $theorems = $theorems->whereIn('id', $this->selectedIds); } - // + apply filters like search and sorting if you want - // then convert them into csv... + + // Apply filters like search and sorting if you want + // then convert them into CSV... return $this->csv($theorems->get()); }+} - // other parts... - {+ public function deleteSelected() { + // ⚠️ Don't forget validation & authorization - // ⚠️ don't forget validation & authorizations - - // Gate::authorize('delete-theorem', User::class); - this->baseQuery()->whereIn('id',$this->selectedIds)->delete() + // Gate::authorize('delete-theorem', Theorem::class); + + $this->baseQuery() + ->whereIn('id', $this->selectedIds) + ->delete(); - // you may clear selection after deletes + // You may clear selection after deletes $this->deselectAll(); }+} + + // other methods... } ``` #### Step 2: Add Bulk Actions UI -Use the `top` slot to add bulk action controls: +Add bulk action controls that appear when rows are selected: ```blade -
+
{+ {{-- BULK ACTIONS --}}
&]:bg-white/5 [[data-open]>&]:ring-2 shadow-sm " > - bulk action + Bulk Actions - + + - export selected csv + Export Selected CSV - delete selected + Delete Selected -
-+} +
+} {{-- SEARCH INPUT --}}
- - + + +
``` ### Add Column Visibility -Let's make status and difficulty hiddeable columns on our theorems table well this is how. +Let's make status and difficulty hideable columns in our theorems table. Here's how: @blade @@ -1122,118 +1112,74 @@ Let's make status and difficulty hiddeable columns on our theorems table well th class="rounded-box ml-2 outline dark:outline-white/20 outline-neutral-900/10 dark:ring-white/15 ring-neutral-900/15 [[data-open]>&]:bg-white/5 [[data-open]>&]:ring-2 shadow-sm" /> + - hidden columns + Hidden Columns - - difficulty + + Difficulty - - status + + Status
+ - - #ID - - - Therem - - - Mathematician - - - Field - - - Year - - + #ID + Theorem + Mathematician + Field + Year + Difficulty - + Status + - - 1 - - - Fundamental Theorem of Calculus - - - Isaac Newton & Gottfried Leibniz - + 1 + Fundamental Theorem of Calculus + Isaac Newton & Gottfried Leibniz Analysis - - 1666 - - + 1666 + 7 - + Proven + - - 2 - - - Fermat's Last Theorem - - - Andrew Wiles - + 2 + Fermat's Last Theorem + Andrew Wiles Number Theory - - 1995 - - + 1995 + 10 - + Proven @@ -1246,159 +1192,124 @@ Let's make status and difficulty hiddeable columns on our theorems table well th @endblade ```blade - -
- - - - + +
+ + + +{+ {{-- HIDDEN COLUMNS --}} + + + + + + + + Hidden Columns + + + + Difficulty + + + Status + + + +} +
+ + + + + + #ID + + Theorem + + Mathematician + + Field + + Year + + + Difficulty + + + Status + + + -{+ {{-- HIDDEN COLUMNS --}} - + @forelse($theorems as $theorem) + - - - + - - - hidden columns - - - - difficulty - - - status - - - +} -
- - - - - - #ID - - - Theorem - - - Mathematician - - - Field - - - Year - - - Difficulty - - - Status - - - - - - @forelse($paginator as $theorem) - - - {{ $theorem->id }} - - - - - - -
- {{ $theorem->mathematician }} -
-
- - - - - {{ $theorem->field }} - - - - -
- {{ $theorem->year_discovered < 0 ? abs($theorem->year_discovered) . ' BC' : $theorem->year_discovered }} -
-
- - - -
- Level {{ $theorem->difficulty_level }} -
-
- - - @if($theorem->is_proven) - - Proven - - @else - - Conjecture - - @endif - -
- - @endforelse -
-
-
+ + +
+ Level {{ $theorem->difficulty_level }} +
+
+ + + @if($theorem->is_proven) + + Proven + + @else + + Conjecture + + @endif + +
+ @empty + + @endforelse +
+
+ ``` #### Persisting Column Preferences @@ -1413,94 +1324,13 @@ x-data="{ This stores the user's column visibility choices in `localStorage`. -### Add Re-Ordering - -Enable drag-and-drop row reordering using Alpine's sort plugin. - -#### Step 1: Enable Draggable Mode - -Add the `draggable` attribute to your table: - -```blade - - - -``` - -This automatically adds: -- A drag handle column at the start -- Sort functionality via Alpine's `x-sort` directive -- Visual feedback during dragging - -#### Step 2: Handle Reorder Events - -Listen for reorder events in your Livewire component: - -```php - 'handleReorder']; - - public function handleReorder($orderedIds) - { - foreach ($orderedIds as $index => $id) { - User::where('id', $id)->update([ - 'sort_order' => $index + 1 - ]); - } - - $this->dispatch('notify', [ - 'message' => 'Order updated successfully', - 'type' => 'success' - ]); - } - - public function render() - { - $theorems = User::query() - ->orderBy('sort_order') - ->paginate($this->perPage); - - return view('livewire.theorems', [ - 'theorems' => $theorems, - ]); - } -} -``` - -#### Dispatch Reorder Event from Alpine - -Add event dispatch to the sortable container: - -```blade - - - -``` - -> **Note:** Row reordering works best with smaller datasets. For paginated tables, consider reordering within the current page only. - -### Add Filters - -Implement advanced filtering with multiple criteria going to add docs for this after adding this capability to the core of the component... +--- -### Playing with Table Design +## Playing with Table Design Customize the table's appearance with utility classes and variants. -#### Bordered Table +### Bordered Table @blade @@ -1535,7 +1365,7 @@ Customize the table's appearance with utility classes and variants.
``` -#### Striped Rows +### Striped Rows @blade @@ -1571,7 +1401,7 @@ Customize the table's appearance with utility classes and variants. ``` -#### Hover Effects +### Hover Effects @blade @@ -1598,12 +1428,12 @@ Customize the table's appearance with utility classes and variants. @endblade ```blade - + ``` -#### Compact Table +### Compact Table @blade @@ -1632,18 +1462,17 @@ Customize the table's appearance with utility classes and variants. ```blade - {{ $user->name }} + {{ $theorem->name }} ``` -#### Custom Loading States +### Custom Loading States ```blade
@@ -1656,295 +1485,7 @@ Customize the table's appearance with utility classes and variants. ``` -## Complete Example - -Here's a full-featured datatable combining all the features: - -```php -baseQuery(); - - if (filled($this->searchQuery)) { - $components = $this->applySearch($components); - } - - if (filled($this->sortBy)) { - $components = $this->applySorting($components); - } - - if (filled($this->selectedIds)) { - $components = $this->applySelection($components); - } - - return $this->csv($components->get()); - } - - public function deleteSelected() - { - $this->validate([ - 'selectedIds' => 'required|array|min:1', - 'selectedIds.*' => 'integer|exists:components,id', - ]); - - $deleted = $this->baseQuery() - ->whereIn('id', $this->selectedIds) - ->delete(); - - $this->selectedIds = []; - - $this->dispatch('notify', [ - 'message' => "{$deleted} components deleted", - 'type' => 'success' - ]); - } - - public function render(): View - { - $components = $this->baseQuery() - ->when(filled($this->sortBy), fn($q) => $this->applySorting($q)) - ->when(filled($this->searchQuery), fn($q) => $this->applySearch($q)) - ->paginate($this->perPage); - - $this->visibleIds = $components->pluck('id') - ->map(fn ($id) => (string) $id) - ->toArray(); - - return view('livewire.components-table', [ - 'components' => $components, - ]); - } - - protected function applySearch($query) - { - $search = str_replace( - ['\\', '%', '_'], - ['\\\\', '\\%', '\\_'], - $this->searchQuery - ); - - return $query->where(function($q) use ($search) { - $q->where('name', 'like', "%{$search}%") - ->orWhere('description', 'like', "%{$search}%"); - }); - } - - protected function sortableColumns(): array - { - return ['name', 'type', 'created_at']; - } - - protected function baseQuery() - { - return ComponentModel::query(); - } -} -``` - -```blade -
- - - -
- - - - Bulk Actions - - - - - - Export Selected CSV - - - - Delete Selected - - - -
- - -
- -
- - - - - - Columns - - - - - - Name - - - Type - - - Created At - - - Description - - - -
- - - - - Name - - - - Type - - - - Created At - - - - Description - - - - - - - - @forelse ($components as $component) - - - {{ $component->name }} - - - - {{ $component->type }} - - - - {{ $component->created_at->format('M d, Y') }} - - - - {{ str($component->description)->limit(100) }} - - - - - - - - - - - Edit - - - Delete - - - - - - @empty - - - - - - -

No results found

-

- Try adjusting your search or create a new component. -

-
-
-
- @endforelse -
-
-
-``` +--- ## Component Props @@ -1954,15 +1495,20 @@ class ComponentsTable extends Component |------|------|---------|-------------| | `paginator` | Paginator\|null | `null` | Laravel paginator instance | | `pagination:variant` | string | `'full'` | Pagination style: `full`, `simple`, `compact` | +| `loadOn` | string | `null` | Comma-separated list of actions to show loading: `pagination`, `search`, `sorting` | | `loading` | slot | `null` | Custom loading indicator | -| `top` | slot | `null` | Content above the table (filters, search, etc.) | | `footer` | slot | `null` | Content below the table | -| `draggable` | boolean | `false` | Enable drag-and-drop reordering | -| `loadOnPagination` | boolean | `false` | Show loading state during pagination | | `wire:loading` | boolean | `false` | Enable Livewire loading states | | `wire:target` | string | `null` | Specific Livewire actions to track | | `class` | string | `''` | Additional CSS classes | +### table.container + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `border` | boolean | `true` | Show container border | +| `class` | string | `''` | Additional CSS classes | + ### table.header | Prop | Type | Default | Description | @@ -1970,6 +1516,12 @@ class ComponentsTable extends Component | `sticky` | boolean | `false` | Make header stick to top while scrolling | | `class` | string | `''` | Additional CSS classes | +### table.columns + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `withCheckAll` | boolean | `false` | Add "select all" checkbox in header | + ### table.head | Prop | Type | Default | Description | @@ -1997,6 +1549,8 @@ class ComponentsTable extends Component | `sticky` | boolean | `false` | Make cell stick while scrolling horizontally | | `class` | string | `''` | Additional CSS classes | +--- + ## Available Traits ### WithPagination @@ -2055,6 +1609,8 @@ public function exportToCsv() } ``` +--- + ## Tips & Best Practices ### Performance Optimization @@ -2068,14 +1624,13 @@ wire:model.live.debounce.300ms="searchQuery" ```blade ``` **3. Eager Load Relationships** ```php -$theorems = User::with('role', 'department') +$theorems = Theorem::with('field', 'mathematician') ->paginate($this->perPage); ``` @@ -2123,7 +1678,7 @@ protected function applySearch($query) ```php protected function sortableColumns(): array { - return ['name', 'email', 'created_at']; + return ['name', 'mathematician', 'year_discovered', 'difficulty_level']; } ``` @@ -2131,7 +1686,7 @@ protected function sortableColumns(): array ```php public function deleteSelected() { - Gate::authorize('delete-multiple', User::class); + Gate::authorize('delete-multiple', Theorem::class); // Delete logic... } @@ -2141,10 +1696,10 @@ public function deleteSelected() The table component is built with accessibility in mind: -- **Keyboard Navigation**: Checkboxes and buttons are fully keyboard accessible -- **Screen Reader Support**: Proper ARIA labels and semantic HTML -- **Focus Management**: Clear focus indicators on interactive elements -- **Sort Indicators**: Visual and semantic indicators for sort state +- **Keyboard Navigation** — Checkboxes and buttons are fully keyboard accessible +- **Screen Reader Support** — Proper ARIA labels and semantic HTML +- **Focus Management** — Clear focus indicators on interactive elements +- **Sort Indicators** — Visual and semantic indicators for sort state ### Common Patterns @@ -2156,7 +1711,6 @@ public function resetAll() $this->sortBy = ''; $this->sortDir = 'asc'; $this->selectedIds = []; - $this->filters = []; $this->resetPage(); } ``` @@ -2190,6 +1744,8 @@ public function export($exportType = 'selected') ``` +--- + ## Troubleshooting ### Checkboxes Not Syncing @@ -2199,7 +1755,7 @@ Ensure you're setting `visibleIds` in your render method: ```php public function render() { - $theorems = User::query()->paginate($this->perPage); + $theorems = Theorem::query()->paginate($this->perPage); // This is crucial for checkbox sync $this->visibleIds = $theorems->pluck('id') @@ -2212,7 +1768,7 @@ public function render() ### Search Not Resetting Pagination -Make sure the `WithSearch` trait's `updatedSearchQuery` is working: +Make sure the `WithSearch` trait's `updatedSearchQuery()` is working: ```php public function updatedSearchQuery() From 43c434ed0ebdba2ec9dfcdc349cbcc6cb33cdc9c Mon Sep 17 00:00:00 2001 From: charrafimed Date: Fri, 23 Jan 2026 16:40:10 +0100 Subject: [PATCH 26/40] wip --- components/table/usage.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/table/usage.md b/components/table/usage.md index 10e39d7..ae44a7f 100644 --- a/components/table/usage.md +++ b/components/table/usage.md @@ -1097,7 +1097,7 @@ Let's make status and difficulty hideable columns in our theorems table. Here's @blade -
+