An open API service indexing awesome lists of open source software.

https://github.com/mustafakhaleddev/filament-responsive-table

Filament Responsive Table
https://github.com/mustafakhaleddev/filament-responsive-table

Last synced: 8 days ago
JSON representation

Filament Responsive Table

Awesome Lists containing this project

README

          

# Filament Responsive Table

Render a Filament list table as stacked cards below a configurable Tailwind breakpoint. Above the breakpoint nothing changes — the native Filament table renders untouched. Below the breakpoint each row becomes a card with column labels, values, and the row's record actions in the footer.

The same `table()` definition drives both views — no duplicate column lists, no second source of truth.

## Requirements

- PHP 8.2+
- Laravel 11+ / 13
- Filament 4 or 5

## Features

- **Breakpoint-driven** — pick `sm`, `md`, `lg`, `xl`, or `2xl`; below it the table becomes cards, above it the native table renders
- **Zero duplication** — columns and record actions come straight from your existing `table()` method
- **Two-column label/value grid** inside each card, matching mobile UX expectations
- **Optional per-card title** resolved from the record
- **Optional bulk-selection checkbox** on each card so bulk actions still work on mobile
- **Optional record actions footer** — stack the row's actions at the bottom of each card
- **Column filtering** — `only()` / `except()` hide columns in cards without affecting the desktop table
- **Custom card Blade view** — opt out of the default template whenever you need it
- **Plugin-level defaults** — set a single default breakpoint for every responsive list page in a panel
- **Three-level configuration cascade** — page overrides plugin overrides config file
- **Dark mode support**

## Installation

```bash
composer require wezlo/filament-responsive-table
```

Optionally register the plugin in your Panel Provider for global defaults:

```php
use Wezlo\FilamentResponsiveTable\FilamentResponsiveTablePlugin;

->plugins([
FilamentResponsiveTablePlugin::make()
->defaultBreakpoint('md'),
])
```

Optionally publish the config:

```bash
php artisan vendor:publish --tag=filament-responsive-table-config
```

### Theme Source (Tailwind v4)

The package's Blade views use Tailwind utility classes. For Tailwind to detect them during your app's build, add the package's views as a `@source` in your Filament custom theme CSS file (usually `resources/css/filament/admin/theme.css`):

```css
@import '../../../../vendor/filament/filament/resources/css/theme.css';

@source '../../../../vendor/wezlo/filament-responsive-table/resources/views/**/*';

@custom-variant dark (&:where(.dark, .dark *));
```

If you don't have a custom theme yet, create one:

```bash
php artisan make:filament-theme
```

Then rebuild assets:

```bash
npm run build
```

## Quick Start

Add the `HasResponsiveTable` trait to your resource's `ListRecords` page and declare a `$responsiveBreakpoint` property to pick the breakpoint:

```php
use Filament\Resources\Pages\ListRecords;
use Wezlo\FilamentResponsiveTable\Concerns\HasResponsiveTable;

class ListUsers extends ListRecords
{
use HasResponsiveTable;

protected static string $resource = UserResource::class;

public ?string $responsiveBreakpoint = 'md';
}
```

That's it. At viewports `< md` (768px) every row collapses into a card showing each column's label on the left and its rendered value on the right. Record actions stack in a footer below.

> The trait does not declare `$responsiveBreakpoint` itself (to avoid PHP trait/class property conflicts), so you declare it on your list page with whatever visibility and default you like — `public`, `protected`, `?string`, or a non-nullable `string` with a default value all work.

## Configuration API

For anything beyond the breakpoint shortcut, implement the `responsiveTable()` method. The configuration from the method wins over the `$responsiveBreakpoint` property.

```php
use Wezlo\FilamentResponsiveTable\Concerns\HasResponsiveTable;
use Wezlo\FilamentResponsiveTable\ResponsiveTableConfiguration;

class ListUsers extends ListRecords
{
use HasResponsiveTable;

public function responsiveTable(ResponsiveTableConfiguration $config): ResponsiveTableConfiguration
{
return $config
->breakpoint('lg')
->except(['id', 'created_at'])
->cardTitle(fn ($record) => $record->name)
->showRecordActions()
->showBulkSelection();
}
}
```

### Configuration Reference

| Method | Signature | Description |
|---|---|---|
| `breakpoint(string)` | `'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | Below this Tailwind breakpoint, rows render as cards. Throws on unknown values. |
| `only(array)` | `array` | Keep only these column names in cards. Desktop table is untouched. |
| `except(array)` | `array` | Hide these column names from cards. Desktop table is untouched. |
| `cardTitle(Closure)` | `fn (Model $record): string\|Htmlable\|null` | Resolve a per-card header title from the record. |
| `showRecordActions(bool)` | `bool` (default `true`) | Render the table's record actions in each card's footer. |
| `showBulkSelection(bool)` | `bool` (default `false`) | Show the bulk-selection checkbox on each card. |
| `cardView(string)` | `string` | Override the default card Blade template. |

### Column Filtering

`only()` and `except()` match against `Column::getName()` — the first argument to `TextColumn::make('name')`, `IconColumn::make('status')`, etc. Nested relationship columns like `client.name` match by that exact name.

```php
$config->only(['name', 'email', 'status']);
// or
$config->except(['id', 'created_at', 'updated_at', 'deleted_at']);
```

### Card Title

Without `cardTitle()`, the card has no header bar — it's just label/value rows and the action footer. Setting it renders a header strip with the title text (and the bulk checkbox if enabled).

```php
$config->cardTitle(fn ($record) => "#{$record->invoice_number}");
$config->cardTitle(fn ($record) => new HtmlString("{$record->name}"));
```

### Custom Card View

Point to your own Blade view if the default layout doesn't fit:

```php
$config->cardView('users.mobile-card');
```

The view receives three variables:

| Variable | Type | Description |
|---|---|---|
| `$record` | `Model` | The Eloquent record for this card |
| `$columns` | `array` | The visible card columns (after `only`/`except`) |
| `$config` | `ResponsiveTableConfiguration` | The resolved configuration |

You can use `$this->getResponsiveTableRecordActions($record)` inside the view to get the cloned, record-bound, visibility-filtered actions array.

## Plugin Configuration

Register the plugin in your Panel Provider to set defaults for all responsive list pages in that panel:

```php
use Wezlo\FilamentResponsiveTable\FilamentResponsiveTablePlugin;

public function panel(Panel $panel): Panel
{
return $panel
->plugins([
FilamentResponsiveTablePlugin::make()
->defaultBreakpoint('md')
->defaultShowRecordActions(true)
->defaultShowBulkSelection(false),
]);
}
```

| Method | Type | Default | Description |
|---|---|---|---|
| `defaultBreakpoint(string)` | `string` | `null` | Default breakpoint when no page sets one |
| `defaultShowRecordActions(bool)` | `bool` | `null` | Default visibility of the actions footer |
| `defaultShowBulkSelection(bool)` | `bool` | `null` | Default visibility of the bulk checkbox |

## Configuration Cascade

Each setting resolves through a four-level cascade:

1. **`responsiveTable()` method** on the `ListRecords` page (highest priority)
2. **`$responsiveBreakpoint` property** on the `ListRecords` page (breakpoint only)
3. **Plugin defaults** on `FilamentResponsiveTablePlugin` in the Panel Provider
4. **Config file** — `config/filament-responsive-table.php` (lowest priority)

The method always wins over the property, which wins over plugin defaults, which win over the config file.

## Default Config File

```php
// config/filament-responsive-table.php
return [
'breakpoint' => 'md',
'show_record_actions' => true,
'show_bulk_selection' => false,
];
```

## Full Example

```php
use Filament\Resources\Pages\ListRecords;
use Wezlo\FilamentResponsiveTable\Concerns\HasResponsiveTable;
use Wezlo\FilamentResponsiveTable\ResponsiveTableConfiguration;

class ListOrders extends ListRecords
{
use HasResponsiveTable;

protected static string $resource = OrderResource::class;

public function responsiveTable(ResponsiveTableConfiguration $config): ResponsiveTableConfiguration
{
return $config
->breakpoint('lg')
->except(['id'])
->cardTitle(fn ($record) => "#{$record->number}")
->showRecordActions()
->showBulkSelection();
}
}
```

The resource's `table()` method stays unchanged — columns, filters, search, header actions, record actions, and bulk actions all carry over to the card view automatically.

## How It Works

- The `HasResponsiveTable` trait overrides `content()` on the `ListRecords` page to render a single wrapper view that contains **both** the native Filament table (via `$this->getTable()->render()`) and a card stack generated from the same columns.
- The wrapper `

` carries `data-breakpoint=""`. A small shipped stylesheet has static `@media` rules — at the configured breakpoint it hides the table and shows the cards, and vice-versa above it. Because the rules are static CSS (not Tailwind utilities), Tailwind's JIT scan isn't required for visibility toggling.
- Each card pulls its columns from `$this->getTable()->getVisibleColumns()`, then applies `only`/`except`. Columns are cloned per record (`$column->getClone()->record($record)`) so the same render pipeline used by the desktop table — badges, icons, date formatting, images — produces the card values.
- Record actions are cloned per record (`$action->getClone()->record($record)`) and filtered by `isHidden()`, mirroring the pattern in Filament's own table Blade view.
- The desktop table is Filament's native `Table::render()` output — search, filters, sorting, pagination, bulk actions, and row actions all work exactly as before.

## CSS Classes

All elements use `fi-responsive-table-*` prefixed classes for targeted styling:

| Class | Element |
|---|---|
| `fi-responsive-table` | Root wrapper (carries `data-breakpoint`) |
| `fi-responsive-table-desktop` | Wraps Filament's native table |
| `fi-responsive-table-cards` | Wraps the card stack |
| `fi-responsive-table-cards-list` | Inner flex container for cards |
| `fi-responsive-table-card` | Individual card |
| `fi-responsive-table-card-header` | Card title + optional checkbox bar |
| `fi-responsive-table-card-title` | Title text |
| `fi-responsive-table-card-checkbox` | Bulk-selection checkbox |
| `fi-responsive-table-card-body` | `

` grid of label/value pairs |
| `fi-responsive-table-card-field` | One label/value pair (uses `display: contents`) |
| `fi-responsive-table-card-field-label` | Column label (`
`) |
| `fi-responsive-table-card-field-value` | Rendered column value (`

`) |
| `fi-responsive-table-card-footer` | Record-actions footer |

Override any of these in your theme CSS to customize the card appearance.

## License

MIT