devletes/filament-timeline-view 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

devletes/filament-timeline-view

Composer 安装命令:

composer require devletes/filament-timeline-view

包简介

Render Filament Tables as a chronological timeline.

README 文档

README

Latest Version on Packagist Total Downloads License GitHub Stars

Render Filament Tables as a chronological timeline. Adds two table macros (->asTimeline() and ->asDoubleSidedTimeline()) plus a TimelineEntry layout column that turns any table query into a feed of date-grouped cards with avatars, time stamps, collapsible day groups, a kebab actions dropdown, and a Load-more button — all wired through Filament's standard query, grouping, action, and pagination APIs.

Requirements

  • PHP ^8.2
  • Filament Tables ^5.0 (the package only requires filament/tables; filament/filament is not needed unless you embed the timeline in a panel)

Installation

composer require devletes/filament-timeline-view

No plugin registration needed — the service provider auto-loads, registers the Table macros, and registers the CSS asset with Filament's asset system. Publish the asset once after install:

php artisan filament:assets

Usage

The package adds two macros to Filament\Tables\Table:

Macro Renders
->asTimeline() Single-column vertical timeline. Cards on the right of the line, dots on the left.
->asDoubleSidedTimeline() Centre line with cards alternating left/right. Collapses to single-column below 768px.

You compose the timeline using Filament's standard table API: ->query(), ->columns(), ->groups(), ->recordActions(), ->recordUrl(), ->paginated(). Just swap ->columns() for TimelineEntry::make() and call one of the timeline macros at the end.

Setting ->recordUrl(...) (or ->recordAction(...)) renders a native Filament "View" link — eye icon plus label — in the card's top-right, separate from the kebab actions.

Resource list page

use Devletes\FilamentTimelineView\Tables\Columns\TimelineEntry;
use Filament\Actions\ActionGroup;
use Filament\Actions\ViewAction;
use Filament\Actions\EditAction;
use Filament\Actions\DeleteAction;
use Filament\Tables\Grouping\Group;
use Filament\Tables\Table;

class ListPulses extends ListRecords
{
    protected static string $resource = PulseResource::class;

    public function table(Table $table): Table
    {
        return $table
            ->columns([
                TimelineEntry::make()
                    ->title('title')
                    ->content('body')
                    ->image('hero_image_url')
                    ->author('author.name', 'author.avatar_url')
                    ->time('published_at'),
            ])
            ->defaultGroup(Group::make('published_at')->date())
            ->recordActions([
                ActionGroup::make([
                    ViewAction::make(),
                    EditAction::make(),
                    DeleteAction::make(),
                ]),
            ])
            ->paginated([10])
            ->asTimeline();
    }
}
Light Dark
Single-sided timeline, light mode Single-sided timeline, dark mode

Dashboard widget

TimelineEntry is a Tables column, so anywhere you can build a Filament\Widgets\TableWidget, you can render a timeline:

use Devletes\FilamentTimelineView\Tables\Columns\TimelineEntry;
use Filament\Tables\Grouping\Group;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget;

class CompanyPulseWidget extends TableWidget
{
    protected int|string|array $columnSpan = 'full';

    public function table(Table $table): Table
    {
        return $table
            ->heading('Company Pulse')
            ->description('Latest updates across the company.')
            ->query(fn () => app(EmployeeDashboardService::class)
                ->pulseFeedQuery(Filament::auth()->user())
                ->with(['author']))
            ->defaultSort('published_at', 'desc')
            ->columns([
                TimelineEntry::make()
                    ->title('title')
                    ->content('excerpt')
                    ->image('hero_url')
                    ->author('author.name', 'author.avatar_url')
                    ->time('published_at'),
            ])
            ->defaultGroup(Group::make('published_at')->date())
            ->recordActions([
                Action::make('view')
                    ->icon('heroicon-m-eye')
                    ->url(fn ($record) => PulsePostResource::getUrl('view', ['record' => $record])),
            ])
            ->paginated([5])
            ->asTimeline();
    }
}

Embed in a custom Dashboard page via Filament\Schemas\Components\Livewire:

use Filament\Schemas\Components\Livewire;

public function content(Schema $schema): Schema
{
    return $schema->components([
        Livewire::make(CompanyPulseWidget::class)->columnSpan('full'),
    ]);
}

Double-sided variant

Swap ->asTimeline() for ->asDoubleSidedTimeline(). Cards alternate per date — the first item of every date always lands on the left, so single-item days stay consistent. Below 768px the layout collapses to a single chronological column.

return $table
    ->columns([TimelineEntry::make()->...])
    ->groups([Group::make('published_at')->date()])
    ->asDoubleSidedTimeline();
Light Dark
Double-sided timeline, light mode Double-sided timeline, dark mode

The TimelineEntry column

TimelineEntry::make() is a layout column (extends Filament\Tables\Columns\Layout\Component). It produces the inner content of one card. The .ftv-card chrome — border, padding, dot, caret, and the actions kebab — is provided by the package blade and applied to every record regardless of which layout column you use.

Field setters

Method Purpose
->title($field) The card title.
->content($field) The card body / excerpt. Newlines render as <br>.
->image($field) A circular image rendered to the left of the body.
->author($name, $avatar = null) A small avatar + name in the card footer.
->time($field, $format = 'g:i A') A small time stamp in the footer. Carbon-parsed.
Light Dark
A single TimelineEntry card, light mode A single TimelineEntry card, dark mode

Each setter accepts either a string field path (resolved via data_get($record, $path)) or a Closure receiving the record:

TimelineEntry::make()
    ->title('title')                                          // dot-notation field path
    ->content(fn ($record) => strip_tags($record->html_body)) // closure
    ->image(fn ($record) => $record->cover?->getUrl('thumb')) // closure
    ->author(
        'author.name',                                         // string path
        fn ($record) => $record->author?->getFilamentAvatarUrl(), // closure
    )
    ->time('published_at')

For time(), a closure may return a Carbon instance (formatted via translatedFormat($format)) or a pre-formatted string (used as-is).

Custom layouts

TimelineEntry is opinionated. If you need a different inner structure, drop it for Stack::make([...]) (or any other column-layout component) and the timeline will still render the dot, caret, card chrome, and actions kebab around it:

use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\Layout\Split;
use Filament\Tables\Columns\Layout\Stack;
use Filament\Tables\Columns\TextColumn;

->columns([
    Stack::make([
        Split::make([
            ImageColumn::make('cover_url')->circular()->size(60)->grow(false),
            Stack::make([
                TextColumn::make('title')->weight('bold')->size('lg'),
                TextColumn::make('summary')->color('gray'),
                Split::make([
                    TextColumn::make('author.name')->size('xs')->color('gray'),
                    TextColumn::make('published_at')->time()->size('xs')->color('gray')->alignEnd(),
                ]),
            ]),
        ]),
    ]),
])
Light Dark
Custom Stack/Split layout, light mode Custom Stack/Split layout, dark mode

Date grouping

Use Filament's standard Group API. When the group is a date (->date()), the package renders a date header above each group with this resolution:

  • TodayToday (translated)
  • Otherwise → the weekday name (Wednesday, Tuesday, …)
  • A second line under the weekday shows M j (e.g. Apr 8), with the year appended for dates outside the current year.
->defaultGroup(Group::make('published_at')->date())

Pass the Group instance straight to ->defaultGroup(...) — no need for a separate ->groups([...]) array unless you want the group selector in the toolbar (which a timeline rarely does, since the grouping is the whole point).

Newest-first ordering. Filament's date grouping orders ascending by default, which puts Today at the bottom. For a typical chronological feed you'll want to flip it on the Group:

->defaultGroup(
    Group::make('published_at')
        ->date()
        ->orderQueryUsing(fn ($query) => $query->orderByDesc('published_at'))
)
Light Dark
Date headers, light mode Date headers, dark mode

Day-group collapse (opt-in)

Day groups are not collapsible by default. To make them collapsible, call ->collapsible() on the Group:

->defaultGroup(Group::make('published_at')->date()->collapsible())

When enabled, each date header gets a chevron toggle that hides/shows the day's cards via Alpine.js (no Livewire round-trip). Collapsed groups show a translated "N posts hidden" pill (collapsed_summary translation key) in place of the cards.

Light Dark
Collapsed day groups, light mode Collapsed day groups, dark mode

Per-card actions

Every record gets a single kebab dropdown in the top-right of the card. The package consumes the table's recordActions(...) config and always renders exactly one ActionGroup per card:

  • If you pass a single ActionGroup → it's used as-is, preserving your ->color(), ->dropdownPlacement(), and other settings.
  • If you pass a flat array of actions, multiple action groups, or a mix → the package extracts every underlying action via getFlatActions() and wraps them in one fresh ActionGroup::make([...])->color('gray').

Either way the visual is one kebab. Hidden actions (->hidden() / ->visible()) are filtered per record before rendering.

->recordActions([
    ActionGroup::make([
        ViewAction::make(),
        EditAction::make()->color('primary'),
        DeleteAction::make()->requiresConfirmation()->color('danger'),
    ])->color('gray'),
])

Action click handlers use Filament's standard mountAction(...) flow, so ->action(fn ($record) => ...), modals, notifications, and forms all work as they would on any normal table.

Light Dark
Open kebab dropdown, light mode Open kebab dropdown, dark mode

Pagination → Load more

->paginated([N]) enables pagination. The package replaces Filament's prev/next page links with a single Load more button that bumps tableRecordsPerPage by N on each click. paginated([5]) shows 5 rows initially and adds 5 more per click.

->paginated([5])

The package renders only the Load more button — Filament's stock per-page selector and prev/next links don't appear. Pass a single-value array like [5] rather than the multi-value form ([5, 10, 25]) — only the first integer is used as the increment, the rest are ignored.

The button hides automatically when the underlying paginator's hasMorePages() returns false. It briefly disables and shows a loading spinner during the request.

If you want vanilla page-link pagination, leave ->paginated(...) set but skip the timeline macro — you'll get Filament's default rendering.

Light Dark
Load more button, light mode Load more button, dark mode

Theming

The package's geometry — line position, card surface, dot colours, caret accent — is driven by a small set of CSS custom properties you can override without forking the stylesheet.

Root-level (.ftv-shell):

Variable Purpose
--ftv-line-color Vertical line, dots, and dashed terminator colour.
--ftv-card-surface Card background (also used as the "halo" around each dot).
--ftv-card-ring Card ring / border colour; also drives the caret colour.
--ftv-hover-shadow-color Card hover shadow.
--ftv-view-link-color Per-card "View" link colour (icon + text). Defaults to --ftv-text-secondary.

Per-group (.ftv-group-body):

Variable Purpose
--ftv-line-x Horizontal offset of the timeline line. Dots and carets derive from this. 1.7rem desktop, 0.65rem mobile.

Dark mode automatically picks up adjusted values via .dark .ftv-shell.

To override per panel, add a rule to your panel's theme CSS:

.fi-ta-timeline {
    /* Override container padding, e.g. flush against the section card. */
    padding: 0;
}

.dark .ftv-shell {
    --ftv-line-color: oklch(50% 0.04 240);
}
Light Dark
Themed timeline, light mode Themed timeline, dark mode

Translations

All user-facing strings go through trans('filament-timeline-view::timeline.*'). Weekday and month names use Carbon's translatedFormat() which respects the app's locale.

To customize strings or add locales, publish the language files:

php artisan vendor:publish --tag=filament-timeline-view-translations

Then edit or add files under lang/vendor/filament-timeline-view/.

Keys:

  • today — "Today" label shown when the group's date is today.
  • load_more — Load more button label.
  • toggle_day — aria-label template for the collapse toggle (:day placeholder).
  • collapsed_summary — pluralized "N posts hidden" shown in the collapsed state.
  • empty_state.heading / empty_state.description — default empty-state copy. Override per-table via ->emptyStateHeading(...) / ->emptyStateDescription(...).

Ships with en and de out of the box.

Light Dark
German locale timeline, light mode German locale timeline, dark mode

Need something custom?

We build production Filament panels and plugins for teams that want to ship fast without compromising on polish. If you need a custom feature, an extended variant of this package, or a fully bespoke component built for your stack, we can help.

Typical engagements: new Filament plugins, custom resources/widgets/actions, theme + UX work, integrations with your existing services, and one-off tailored forks of our open-source packages.

Credits

License

MIT. See LICENSE.md.

devletes/filament-timeline-view 适用场景与选型建议

devletes/filament-timeline-view 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 5.18k 次下载、GitHub Stars 达 22, 最近一次更新时间为 2026 年 04 月 27 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「timeline」 「laravel」 「table」 「filament」 「filament-plugin」 「filament-table」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 devletes/filament-timeline-view 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 devletes/filament-timeline-view 我们能提供哪些服务?
定制开发 / 二次开发

基于 devletes/filament-timeline-view 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 5.18k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 22
  • 点击次数: 34
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 22
  • Watchers: 0
  • Forks: 2
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-04-27